【问题标题】:Question on C++ constructor exception safetyC++构造函数异常安全问题
【发布时间】:2021-06-21 23:45:59
【问题描述】:

来自“Exceptional C++”,第 27 页,作者正在谈论 Stack 的这段代码:

template< typename T>
class Stack {
public:
  Stack();
  ~Stack();
private:
  T* v_;
  size_t vsize_;
  size_t vused_;
};

template<typename T>
Stack<T>::Stack() :v_(0), vsize_(10), vused_(0) {
  v_ = new T[vsize_];
}
template<typename T>
Stack<T>::~Stack() {
  delete[] v_;
}

在第 27 页和第 28 页上,它指出当 T 的构造函数抛出时,这不会泄漏。引用本书:

其次,T 的默认构造函数,它可能会抛出任何东西,在这种情况下,任何构造的对象都会被销毁,分配的内存会自动保证通过 operator delete[])() 释放

但是,我不明白T 的析构函数和delete[]T 的构造函数抛出时自动调用的原因。这里没有try/catch,构造函数完成一半不会触发析构函数。我的理解是我们需要在构造函数中清理自己的烂摊子,而我认为这里没有完成。

我在我自己的 gcc10 环境中仔细检查了,我没有看到T 的析构函数在被抛出时被调用。例如,如果我使用下面的Thrower 创建堆栈,我看不到析构函数行打印。

我错过了什么吗?

int created = 0;
class Thrower {
public:
  Thrower() {
    if (created++ == 2) {
      throw overflow_error("overflow!");
    }
  }
  ~Thrower() {
    cout << "destructor called";
  }
};

【问题讨论】:

  • Thrower 析构函数应该有 &lt;&lt; endl; 因为不需要实现来刷新不完整的输出行
  • 注意:引用并没有说你的析构函数会被调用。

标签: c++ exception


【解决方案1】:

我错过了什么吗?

是的,这部分引用的文字:

其次,T 的默认构造函数,它可能会抛出任何东西,在这种情况下,任何构造的对象都会被销毁,并且分配的内存会自动保证通过 operator delete[] 释放)( )

使用new[] 表达式创建数组时,会为数组分配内存,然后在该内存中按顺序构造元素。如果任何元素的构造函数抛出,任何已经构造的对象都会被自动销毁,但构造函数抛出的对象不会,然后数组的内存会被自动释放。这一切都发生在 new[] 表达式内部,这就是为什么您看不到它的任何代码。

并且由于Stack的构造函数中没有try/catch,所以T的构造函数抛出的任何异常都会在数组创建中止后继续向上传播调用堆栈,从而中止Stack 的构造函数(所以Stack 的析构函数不会被调用),依此类推,直到捕获到异常,或者如果没有捕获到则进程终止。

【讨论】:

  • 另外:需要注意的是,清理是针对表达式,而不是构造函数。所以:如果有 两个 new 表达式,第二个可以抛出,第一个会泄漏。
【解决方案2】:

new[] 表达式的部分规范是,如果 T 构造函数之一抛出,则实现必须销毁所有早期的 T 并释放获得的存储空间。

Stack 无关;您可以通过创建一些有时会在构造函数中抛出的类并执行 int main() { try { new Myclass[100]; } catch(...) {} }

来观察程序中的这种行为

请注意,您应该有一个匹配的 catch 处理程序以观察行为 - 允许未捕获的异常终止程序而不执行任何中间对象销毁。

【讨论】:

  • 即使他捕捉到了异常?
  • @DaviMendesDev 是的,旧对象的释放将在执行进入 catch 处理程序之前完成。 (但如果没有处理程序,则允许程序终止而不释放旧对象)
  • 是的!捕获异常后,我能够看到 T 的析构函数被调用!
猜你喜欢
  • 2015-05-08
  • 2013-06-25
  • 1970-01-01
  • 1970-01-01
  • 2023-04-07
  • 1970-01-01
  • 1970-01-01
  • 2010-11-14
  • 1970-01-01
相关资源
最近更新 更多