在析构函数c ++中访问所有者

时间:2016-06-22 06:22:56

标签: c++ smart-pointers ownership-semantics

假设有一个对象A通过std::unique_ptr<B>拥有一个对象B.进一步B保持对A的原始指针(弱)引用。然后A的析构函数将调用B的析构函数,因为它拥有它。

在B的析构函数中访问A的安全方法是什么? (因为我们也可能在A的析构函数中。)

一种安全的方法是在A的析构函数中明确重置对B的强引用,以便以可预测的方式销毁B,但是一般的最佳实践是什么?

5 个答案:

答案 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;
}

Live demo

因为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;
}

Live demo

因为n2B尝试使用它时已被销毁。

答案 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指针是多余的 只需确保相应的成员函数是私有的,这样只能以预期的方式使用该接口。

<强>备注: 如果您自己完全控制AB之间的通信,这是一个简单的轻量级解决方案。 不要在任何情况下都将其设计为网络协议!这将需要更多的安全围栏。

答案 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。