假设有一个对象A通过std::unique_ptr<B>
拥有一个对象B.进一步B保持对A的原始指针(弱)引用。然后A的析构函数将调用B的析构函数,因为它拥有它。
在B的析构函数中访问A的安全方法是什么? (因为我们也可能在A的析构函数中。)
一种安全的方法是在A的析构函数中明确重置对B的强引用,以便以可预测的方式销毁B,但是一般的最佳实践是什么?
答案 0 :(得分:3)
在B的析构函数中访问A的安全方法是什么? (因为我们也可能在A的析构函数中。)
没有安全的方法:
3.8 / 1
[...]类型T的对象的生命周期结束时:
- 如果T是具有非平凡析构函数(12.4)的类类型,则析构函数调用开始[...]
我认为在生命终结之后你无法访问对象是直截了当的。
3.8 / 5
在对象的生命周期开始之前但在对象将占用的存储之后 在对象的生命周期结束后,在对象占用的存储空间之前, 重用或释放任何指向对象所在或位于的存储位置的指针 可以使用但仅限于有限的方式。 对于正在建造或销毁的物体,请参阅12.7 。除此以外, 这样的指针指的是已分配的存储(3.7.4.2),并且使用指针就像指针的类型为void *一样, 是很好的定义。可以取消引用这样的指针,但是所得到的左值可以仅在有限的情况下使用 方式,如下所述。如果出现以下情况,该程序具有未定义的行为 [...]
在12.7中,列出了在施工和销毁过程中可以做的事情,其中一些是最重要的:
12.7 / 3:
显式或隐式地将指针(一个glvalue)引用引用到类X的对象到指针(引用)
的直接或间接基类B ,X的构造及其所有直接构造或 直接或间接从B派生的间接基础应该已经开始,这些类的破坏不应该已经完成,否则转换将导致未定义的行为。要形成一个指针(或 访问对象obj的直接非静态成员的值,obj的构造应该已经开始 并且其破坏不得完成,否则计算指针值(或访问 成员价值)导致未定义的行为。
12.7 / 4
会员功能,包括虚拟功能(10.3),可以在构建或销毁(12.6.2)中调用。 当从构造函数或析构函数直接或间接调用虚函数时,包括 在构造或破坏类的非静态数据成员期间,以及对象的对象 call apply是在构造或销毁下的对象(称之为x),被调用的函数是最终的覆盖 在构造函数或析构函数的类中,而不是在更多派生类中重写它。如果是虚拟的 函数调用使用显式类成员访问(5.2.5),对象表达式引用完整 x的对象或该对象的基类子对象之一,但不是x或其基类子对象之一, 行为未定。
答案 1 :(得分:3)
我不是语言律师,但我认为没关系。你正在踩着危险的地方,也许应该重新考虑你的设计,但如果你小心,我认为你可以依靠members are destructed in the reverse order they were declared的事实。
所以这没关系
#include <iostream>
struct Noisy {
int i;
~Noisy() { std::cout << "Noisy " << i << " dies!" << "\n"; }
};
struct A;
struct B {
A* parent;
~B();
B(A& a) : parent(&a) {}
};
struct A {
Noisy n1 = {1};
B b;
Noisy n2 = {2};
A() : b(*this) {}
};
B::~B() { std::cout << "B dies. parent->n1.i=" << parent->n1.i << "\n"; }
int main() {
A a;
}
因为A
的成员按n2
然后b
然后n1
被破坏。但这不行
#include <iostream>
struct Noisy {
int i;
~Noisy() { std::cout << "Noisy " << i << " dies!" << "\n"; }
};
struct A;
struct B {
A* parent;
~B();
B(A& a) : parent(&a) {}
};
struct A {
Noisy n1 = {1};
B b;
Noisy n2 = {2};
A() : b(*this) {}
};
B::~B() { std::cout << "B dies. parent->n2.i=" << parent->n2.i << "\n"; }
int main() {
A a;
}
因为n2
在B
尝试使用它时已被销毁。
答案 2 :(得分:0)
正如已经提到的,没有&#34;安全的方式&#34;。事实上,正如PcAF所指出的那样,A
的生命周期已经在你到达B
的析构函数时结束了。
我也想指出这实际上是件好事!必须有一个严格的对象被摧毁的顺序
现在你应该做的是事先告诉B
A
即将被毁坏。
它就像
void ~A( void ) {
b->detach_from_me_i_am_about_to_get_destructed( this );
}
根据设计ob this
,可能需要或不传递B
指针(如果B
包含许多引用,则可能需要知道要分离哪一个。如果只是保持一个,this
指针是多余的
只需确保相应的成员函数是私有的,这样只能以预期的方式使用该接口。
<强>备注强>:
如果您自己完全控制A
和B
之间的通信,这是一个简单的轻量级解决方案。 不要在任何情况下都将其设计为网络协议!这将需要更多的安全围栏。
答案 3 :(得分:0)
考虑一下:
struct b
{
b()
{
cout << "b()" << endl;
}
~b()
{
cout << "~b()" << endl;
}
};
struct a
{
b ob;
a()
{
cout << "a()" << endl;
}
~a()
{
cout << "~a()" << endl;
}
};
int main()
{
a oa;
}
//Output:
b()
a()
~a()
~b()
“然后A的析构函数将调用B的析构函数,因为它拥有它。” 这不是在复合的情况下调用析构函数的正确方法对象。如果你看到上面的例子,那么第一个a
被破坏,然后b
被破坏。 a
的析构函数不会调用b
的析构函数,以便控件返回a
的析构函数。
“在B的析构函数中访问A的安全方法是什么?” 。如上所示,a
已被销毁,因此无法在a
的析构函数中访问b
。
“因为我们也可能在A的析构函数中。” 。这是不正确的。同样,当控件退出a
的析构函数时,只有控件进入b
的析构函数。
析构函数是类T的成员函数。一旦控件退出析构函数,就无法访问类T.类T的所有数据成员都可以在类T的构造函数和析构函数中访问,如上例所示。
答案 4 :(得分:-2)
如果你只看两个A和B类的关系,结构很好:
CREATE TABLE tmp_table AS(
SELECT accounts.id, username, password, proxy.ip, proxy.port
FROM accounts
LEFT JOIN proxy ON proxy.id = accounts.proxy_id
ORDER BY RAND()
LIMIT 1);
如果A和B的对象扩散到其他程序单元,则会出现问题。然后你应该使用std :: memory中的工具,比如std :: shared_ptr或std:weak_ptr。