这是与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()
的{{1}}的调用及其后续访问其实际被摧毁的ListDerived
成员。)
第二个问题是,如果这不是UB只是因为destroyed
有一个简单的析构函数,如果它更复杂(例如a destroyed
),它会变成UB吗?
第三个问题是(假设这是UB),仍有相同流量但避开UB的好方法是什么?我对Real Code™有一些限制:
shared_ptr
的析构函数开始执行,就可以跳过对g()
的调用。ListDerived
析构函数不允许访问ListDerived
中的任何内容(也不允许保留其特殊项目的单独副本),因此它无法标记该项目某种方式告诉它以避免调用items
。g()
本身不能在ListDerived
中假设它,因此无法使用shared_ptr
。(可能会有更多的限制因素会使我无法想到的替代解决方案复杂化 - 这些限制措施的灵感来自于我在编写时考虑并拒绝的解决方案。)
答案 0 :(得分:0)
这是我自己尝试解决这个问题(假设它确实是UB),但我有兴趣听到我错了,原始代码是否正常,或者是否有&#39 ;比下面更好的解决方案:
shared_from_this
这引入了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
中)(在{{ 1}})。我认为现在这是安全的。
(当然,除非并发通话,特别是因为~ListDerived()
不是~ListBase()
内的拥有指针。如果复制了p
,它也不安全移动了,但后来也没有原始代码;假装已经通过常规方式阻止了。)
f()
现在已经多余了,但是我把它留了下来以避免过多地更改代码。 (由于C ++ 03,也使用ListDerived
;如果你有destroyed
或boost::shared_ptr
,可以随意交换,它不应该有所作为。)
使非拥有std::tr1::shared_ptr
感觉有点不对(即使official cookbook中包含它),但AFAIK没有任何支持终身追踪的其他标准类型