gpt4 book ai didi

c++ - 销毁期间派生的成员(member)国

转载 作者:太空宇宙 更新时间:2023-11-04 13:04:25 29 4
gpt4 key购买 nike

这是与 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? Real Code™ 对此有一些限制:

  • 它是 C++03 代码 (VS2008),很遗憾不允许使用 C++11。 (话虽如此,我仍然有兴趣听听 C++11 是否会以某种方式改进。)
  • 一旦 ListDerived 的析构函数开始执行,就可以跳过对 g() 的调用。
  • ListDerived 的析构函数不允许访问 items 中的任何内容(也不允许保留其特殊项目的单独拷贝),因此它无法标记该项目以某种方式告诉它避免调用 g()
  • ListDerived 本身不能假定它在 shared_ptr 中,因此不能使用 shared_from_this

(可能有更多的限制会使我没有想到的替代解决方案变得复杂——这些是受到我在撰写本文时考虑并拒绝的解决方案的启发。)

最佳答案

这是我自己解决这个问题的尝试(假设它确实是 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_ptrstd::shared_ptr,请随意交换,应该不会有什么不同。)

制作非拥有shared_ptr感觉有点不对(即使它包含在 official cookbook 中)但据我所知,没有任何其他标准类型支持生命周期跟踪。

关于c++ - 销毁期间派生的成员(member)国,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43039269/

29 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com