销毁期间派生的会员国
Derived member states during destruction
这是一个与 this 类似的问题,但它侧重于虚拟方法(在问题和答案中),我对派生的非虚拟方法和数据成员更感兴趣 class 以及它如何与相关的类型层次结构交互。在这个测试代码中:
#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;
}
(由于针对此问题进行了简化,此代码存在许多已知错误——我知道它会泄漏内存并产生过多内容 public,等等;这不是重点.)
这个测试程序的输出结果如下:
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
特别注意,在 class 析构函数中对 iterate()
的调用导致了对 ListDerived::g()
的调用,在 ~ListDerived()
执行其主体之前实际上已经退出了——所以 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
中的任何内容(也不允许保留其特殊项目的单独副本),因此它无法以某种方式标记该项目告诉它避免调用 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_ptr
或 std::shared_ptr
,请随意交换,应该不会有什么区别。)
创建非拥有 shared_ptr
感觉有点不对(即使它在 official cookbook 中有所介绍)但据我所知,没有任何其他标准类型支持生命周期跟踪。
这是一个与 this 类似的问题,但它侧重于虚拟方法(在问题和答案中),我对派生的非虚拟方法和数据成员更感兴趣 class 以及它如何与相关的类型层次结构交互。在这个测试代码中:
#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;
}
(由于针对此问题进行了简化,此代码存在许多已知错误——我知道它会泄漏内存并产生过多内容 public,等等;这不是重点.)
这个测试程序的输出结果如下:
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
特别注意,在 class 析构函数中对 iterate()
的调用导致了对 ListDerived::g()
的调用,在 ~ListDerived()
执行其主体之前实际上已经退出了——所以 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
中的任何内容(也不允许保留其特殊项目的单独副本),因此它无法以某种方式标记该项目告诉它避免调用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_ptr
或 std::shared_ptr
,请随意交换,应该不会有什么区别。)
创建非拥有 shared_ptr
感觉有点不对(即使它在 official cookbook 中有所介绍)但据我所知,没有任何其他标准类型支持生命周期跟踪。