【问题标题】:handling failing constructors处理失败的构造函数
【发布时间】:2013-11-12 16:18:16
【问题描述】:

我正在阅读来自 C++ FAQ 的失败构造函数,但不理解以下代码。

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 指向的内存泄漏。

【问题讨论】:

  • 您遗漏了第 4 步,释放了 p 的内存。为什么你认为这是不可能的?
  • 我可以知道 Y 的内部结构可能会在 Y 的构造函数中应用 RAII,但是删除 p 是什么?
  • ::operator new 承诺分配内存,调用构造函数,如果抛出再次释放。很容易实现。
  • 知道了!所以在“特殊”情况下 new 也会调用删除。
  • @goldcode 没错

标签: c++ exception constructor


【解决方案1】:

如果 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 块,所以如果你需要,比如说,六个局部变量,它将会被深度嵌套并且非常难看。

【讨论】:

  • 我更喜欢使用智能指针对所有这些对象进行 RAIIing。我只是不知道新运营商也适用于 RAII。
  • @goldcode:是的,在大多数情况下,您希望每个对象都自行管理,并消除所有 try 块等。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    一切都取决于Y 的构造函数。该示例假定构造函数定义明确,并且在抛出之前处理了构造函数内部的所有内容。

    使用 RAII 和智能指针(例如 std::unique_ptr)有助于编写构造函数的安全抛出。例如:

    struct Foo
    {
        Foo() : a(new A)
        {
            throw std::runtime_error("test");
        }
    
        std::unique_ptr<A> a;
    };
    

    a 将被安全清理。

    【讨论】:

      【解决方案4】:

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

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

      这样做的缺点是,如果构造函数执行通常由析构函数执行的需要清理的操作,那么现在构造函数有责任在出现异常时执行清理。举个例子:

      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。这样一来,没有一个类负责管理多个资源,并且不会再出现上述情况。

      【讨论】:

        猜你喜欢
        • 2012-07-05
        • 1970-01-01
        • 2019-01-03
        • 2011-01-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-06-28
        • 1970-01-01
        相关资源
        最近更新 更多