【发布时间】:2023-03-24 09:35:01
【问题描述】:
这是一个与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() 执行其主体之后但在它实际退出之前调用ListDerived::g()——所以ListDerived 实例是在出去的路上,但仍然部分活着。请注意g() 本身不是虚拟的,并且不在ListBase 的方法中。
我怀疑这个输出依赖于 UB,所以这是第一个问题:是这种情况还是这个定义明确(尽管可能是狡猾的风格)?
(有问题的行为是在部分破坏的ListDerived 上调用g() 以及随后访问其实际破坏的destroyed 成员。)
第二个问题是,如果这不是 UB,仅仅是因为 destroyed 有一个微不足道的析构函数,那么如果它是更复杂的东西(例如 shared_ptr),它会变成 UB 吗?
第三个问题是(假设这是UB),有什么好的方法可以保持相同的流量但避免UB? Real Code™ 对此有一些限制:
- 这是 C++03 代码 (VS2008),遗憾的是不允许使用 C++11。 (话虽如此,我仍然很想知道 C++11 是否会以某种方式改进。)
- 一旦
ListDerived的析构函数开始执行,就可以跳过对g()的调用。 -
ListDerived的析构函数不允许访问items中的任何内容(也不允许保留其特殊项目的单独副本),因此它无法以某种方式标记该项目以告诉它避免调用 @987654339 @。 -
ListDerived本身不能假设它在shared_ptr中,所以不能使用shared_from_this。
(可能有更多的限制会使我没有想到的替代解决方案复杂化——这些是受到我在写这篇文章时考虑并拒绝的解决方案的启发。)
【问题讨论】:
-
我很难理解您认为代码应该的行为方式。如果你能列出要求,可能会更容易理解。
-
我已经在底部列出了要求。基本思想是
ListBase有一个项目列表,它通过调用项目上的虚拟方法来累积并定期处理这些项目。在销毁时,它仍然需要处理列表中的任何未处理项目(在 Real Code™ 中,它只是清理处理而不是完整处理,但差异对问题无关紧要)。但是有一个派生类型偶尔需要排队一些需要调用派生类型本身来完成工作的东西,但是由于结构的原因,这在销毁处理时很棘手。
标签: c++ polymorphism destructor c++03