处理失败的构造函数

handling failing constructors

本文关键字:构造函数 失败 处理      更新时间:2023-10-16

我正在从常见问题解答中读取失败C++构造函数,但不理解以下代码。

void f()
{
  X x;             ← if X::X() throws, the memory for x itself will not leak
  Y* p = new Y();  ← if Y::Y() throws, the memory for *p itself will not leak
}

如果构造函数抛出,p 指向的内存怎么可能不泄漏?我假设顺序如下。

  1. 为对象 Y 分配内存。
  2. 调用 Y 的构造函数。
  3. Y 的构造函数抛出和 p 泄漏指向的内存。
如果 Y 的

构造函数抛出,则堆栈将展开,包括删除分配给 Y 的内存。

问题主要出现在/如果您有多个对象要处理时。例如:

void f() { 
    X *x = new X();
    Y *y = new Y();
}

现在,如果new X()部分成功,但new Y()部分失败,则分配给y的内存将被删除,x不会被销毁,其内存将被泄漏。如果你真的坚持,你可以用try块来解决这个问题:

try { 
    X *x = new X();
    Y * y = new Y();
}
catch (y_construction_failed) {
    delete x;
}

这样做的最大问题是,如果你有两个以上的项目,你必须嵌套try块,所以如果你需要,比如说,六个局部变量,它将是深深嵌套的,而且非常丑陋。

你会遇到一个类似的问题,函数void f(X*, Y*)并调用f(new X(),new Y())。如果其中一个新调用成功,而另一个失败,则存在内存泄漏。要解决此问题,您可以创建其他函数"X* make_X()"和"Y* make_Y()"返回指针。现在,f(make_X(),make_Y())是安全的。(走那么远之后,你可能会使用智能指针)

请务必注意,即使删除了对象,在这种情况下也不会调用其析构函数。

这是有道理的,因为构造期间的异常表明对象从未完全构造过(即尚未建立其类不变量),因此调用析构函数可能是危险的。

这样做的缺点是,如果构造函数执行的操作需要清理,而这些操作通常由析构函数执行,则构造函数现在负责在发生异常时执行此清理。举个例子:

class C {
private:
    int* p1;
    int* p2;
public:
    C() : p1(new int()), p2(new int()) {}
    ~C() { delete p1; delete p2; }
};

如果抛出分配p2,则已分配给p1的内存将泄漏。作为程序员,您有责任以不可能发生的方式编写构造函数。

实现此目的的最简单方法是将资源管理职责委托给 RAII 容器类,如 unique_ptr 。这样,没有类负责管理多个资源,并且不会再发生上述情况。