这与this类似,但重点是虚拟方法(在问答中),我对派生类的非虚拟方法和数据成员以及它如何与相关类型交互更感兴趣。层次结构。在此测试代码中:
#include <iostream>
#include <vector>
using namespace std;
struct ItemBase
{
explicit ItemBase(int v) : value(v) {}
virtual ~ItemBase() {}
virtual void f() { cout << "In ItemBase::f() for " << value << endl; }
int value;
};
struct ListBase
{
virtual ~ListBase() { cout << "In ~ListBase" << endl; iterate(); }
void add(int v) { items.push_back(new ItemBase(v)); }
void iterate()
{
for (vector<ItemBase*>::iterator it = items.begin(); it != items.end(); ++it)
{
(*it)->f();
}
items.clear();
}
vector<ItemBase*> items;
};
struct ListDerived : public ListBase
{
struct ItemDerived : public ItemBase
{
ItemDerived(int v, ListDerived& p) : ItemBase(v), owner(p) {}
virtual void f()
{
cout << "In ItemDerived::f() for " << value << endl;
owner.g();
}
ListDerived& owner;
};
void addSpecial(int v) { items.push_back(new ItemDerived(v, *this)); }
ListDerived() : destroyed(false) {}
~ListDerived() { cout << "In ~ListDerived" << endl; destroyed = true; }
void g() { cout << "In ListDerived::g(): " << (destroyed ? "dead" : "alive") << endl; }
bool destroyed;
};
int main()
{
ListDerived list;
list.add(1);
list.addSpecial(2);
list.iterate();
list.add(3);
list.addSpecial(4);
return 0;
}
(此代码中存在许多已知的错误,因为此问题已简化了该代码-我知道它会泄漏内存并向公众公开过多的内容;这不是重点。)
该测试程序的输出如下:
In ItemBase::f() for 1
In ItemDerived::f() for 2
In ListDerived::g(): alive
In ~ListDerived
In ~ListBase
In ItemBase::f() for 3
In ItemDerived::f() for 4
In ListDerived::g(): dead
特别要注意的是,在
iterate()
执行其主体之后但实际退出之前,对基类析构函数中的ListDerived::g()
的调用导致对~ListDerived()
的调用-因此ListDerived
实例正在进行中出去,但仍然部分活着。请注意,g()
本身不是虚拟的,也不属于ListBase
的方法。我怀疑此输出依赖于UB,所以这是第一个问题:是这种情况还是定义得很好(尽管可能是狡猾的风格)?
(有问题的行为是对部分销毁的
g()
上的ListDerived
的调用,以及随后对实际销毁的destroyed
成员的访问。)第二个问题是,如果不是仅仅因为
destroyed
具有琐碎的析构函数就不是UB,那么如果它更复杂(例如shared_ptr
),它会变成UB吗?第三个问题是(假设这是UB),什么是保持相同流量但避免UB的好方法?我在Real Code™中对此有一些限制:
它是C ++ 03代码(VS2008),因此遗憾的是不允许使用C ++ 11。 (话虽如此,我仍然很想知道C ++ 11是否会以某种方式改进它。)
一旦
g()
的析构函数开始执行,可以跳过对ListDerived
的调用。ListDerived
的析构函数不允许访问items
中的任何内容(也不保留其特殊项目的单独副本),因此它不能以某种方式标记该项目以使其避免调用。g()
本身不能假定它在ListDerived
中,因此不能使用shared_ptr
。(可能会有更多的约束使我从未想到的替代解决方案变得复杂-这些灵感来自于我在撰写本文时考虑并拒绝的解决方案。)
最佳答案
这是我自己解决这个问题的尝试(假设确实是UB),但是我有兴趣了解我是否错了,并且原始代码还可以,或者是否有比以下更好的解决方案:
struct ListDerived : public ListBase
{
struct ItemDerived : public ItemBase
{
ItemDerived(int v, boost::shared_ptr<ListDerived> const& p)
: ItemBase(v), owner(p) {}
virtual void f()
{
cout << "In ItemDerived::f() for " << value << endl;
if (boost::shared_ptr<ListDerived> p = owner.lock())
{
p->g();
}
}
boost::weak_ptr<ListDerived> owner;
};
struct null_deleter
{
void operator()(void const *) const {}
};
void addSpecial(int v) { items.push_back(new ItemDerived(v, token)); }
ListDerived() : destroyed(false) { token.reset(this, null_deleter()); }
~ListDerived() { cout << "In ~ListDerived" << endl; destroyed = true; }
void g() { cout << "In ListDerived::g(): " << (destroyed ? "dead" : "alive") << endl; }
bool destroyed;
boost::shared_ptr<ListDerived> token;
};
这引入了
token
,它存在于ListDerived
实例的整个生命周期中(类似于shared_from_this
),而实际上并不拥有该实例(因此是null_deleter
),但是仍然可以用来制作weak_ptr
这些项用于访问其父项,而不是直接使用引用。结果输出:In ItemBase::f() for 1
In ItemDerived::f() for 2
In ListDerived::g(): alive
In ~ListDerived
In ~ListBase
In ItemBase::f() for 3
In ItemDerived::f() for 4
因此,对
g()
的第一个调用按预期发生,但是第二个调用从未执行,因为token
在到达它之前(在~ListDerived()
中)已经被销毁(在~ListBase()
中)。我认为这现在很安全。(当然,禁止并发调用,尤其是由于
p
不是f()
内的所有指针。如果复制或移动ListDerived
也不是安全的,但是原始代码也都不安全;假装通过通常的方法。)destroyed
现在是多余的,但为了避免过多更改代码,我将其保留了下来。 (由于C ++ 03的原因,也使用boost::shared_ptr
;如果有它们,也可以随意交换std::tr1::shared_ptr
或std::shared_ptr
,这没有什么区别。)制作一个没有所有权的
shared_ptr
感觉有点不对劲(即使official cookbook涵盖了它),但是AFAIK没有其他支持寿命跟踪的标准类型。关于c++ - 销毁期间的 dispatch 成员国,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/43039269/