在演讲中,以下代码已被证明是不安全的,因为如果构造函数抛出,则不会调用析构函数并泄漏资源:
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;
};
建议解决方案是使用委托构造函数,如下所示:
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;
};
这是安全的,因为:
C ++ 11 15.2 [except.ctor] / 2:“如果是非委托构造函数的话 对象已完成执行,并为此委托构造函数 对象存在异常,对象的析构函数将是 调用“。
然而,在同一张幻灯片中,它说:
仅仅因为你可以利用这条规则并不意味着你应该这样做!
如果确保此代码安全,那么它有哪些潜在问题?
答案 0 :(得分:6)
仅仅因为某些东西是安全的并不意味着这样做是个好主意。
例如,使用该委托构造函数调用对于异常安全至关重要,但对于不熟悉该语言错综复杂的代码的随意读者而言,这一点并不明确。一个月后,看到你的代码的其他人可能会想“如果你再次在构造函数体中设置它,为什么要将它设置为null?”并删除它。哎哟。
此外,当您手动管理生命周期时,您需要编写自己的复制/移动构造函数/赋值运算符。小姐和破坏结果。如果使用unique_ptr
来管理生命周期,那么编译器生成的移动构造函数/赋值运算符和析构函数将执行正确的操作,如果您尝试复制而不自行实现复制构造函数,它会抱怨。
答案 1 :(得分:1)
这一点当然不清楚。有人可以通过仅在一个构造函数中添加新变量来到达并修改代码,而无需了解第二个构造函数的有用性。 您应该考虑使用std::unique_ptr
class TwoResources
{
TwoResources(int x, int y) : m_a( new A( x ) ), m_b( new B( y ) )
{
}
std::unique_ptr<A> m_a;
std::unique_ptr<B> m_b;
};
另外,看看std::make_unique比使用new和delete运算符手动更安全:
class TwoResources
{
TwoResources(int x, int y):m_a( std::make_unique<A>( x ) ), m_b( std::make_unique<B>(B( y ) )
{
}
std::unique_ptr<A> m_a;
std::unique_ptr<B> m_b;
};