使用委托构造函数来避免泄漏

use*_*979 4 c++ memory-leaks c++11

在一次谈话中,以下代码已被证明是不安全的,因为如果构造函数抛出,则不会调用析构函数并泄漏资源:

class TwoResources {
    TwoResources(int x, int y)
        : m_a(nullptr), m_b(nullptr) {
        m_a = new A(x); m_b = new B(y);
    }
    ~TwoResources() {
        delete m_b; delete m_a;
    }
    A * m_a; B * m_b;
};
Run Code Online (Sandbox Code Playgroud)

建议解决方案是使用委托构造函数,如下所示:

class TwoResources {
    TwoResources() : m_a(nullptr), m_b(nullptr) { }
    TwoResources(int x, int y) : TwoResources() {
        m_a = new A(x); m_b = new B(y);
    }
    ~TwoResources() {
        delete m_b; delete m_a;
    }
    A * m_a; B * m_b;
};
Run Code Online (Sandbox Code Playgroud)

这是安全的,因为:

C++ 11 15.2 [except.ctor]/2:"如果对象的非委托构造函数已完成执行,并且该对象的委托构造函数存在异常,则将调用该对象的析构函数."

但是在同一张幻灯片中,它说:

仅仅因为你可以利用这条规则并不意味着你应该这样做!

如果这个代码保证是安全的,那么它有哪些潜在的问题呢?

T.C*_*.C. 6

只是因为某些东西是安全的并不意味着这样做是个好主意.

例如,使用该委托构造函数调用对于异常安全至关重要,但对于不熟悉该语言错综复杂的代码的随意读者来说,这一点并不明确.一个月后,看到你的代码的其他人可能会想"如果你再次在构造函数体中设置它,为什么要将它设置为null?" 并删除它.哎哟.

此外,当您手动管理生命周期时,您需要编写自己的复制/移动构造函数/赋值运算符.小姐和破坏结果.如果使用a unique_ptr来管理生命周期,那么编译器生成的移动构造函数/赋值运算符和析构函数将执行正确的操作,如果您尝试复制而不自行实现复制构造函数,它将会抱怨.