【问题标题】:Is calling destructor from a catch block in constructor safe?从构造函数中的 catch 块调用析构函数是否安全?
【发布时间】:2019-02-18 23:20:18
【问题描述】:

在我的构造函数中,如果其中的任何代码抛出,我必须销毁任何剩余的资源。我想避免编写重复的代码,所以我只在 catch 块中调用析构函数,而不是释放任何已创建的资源。这安全吗?

我知道如果构造函数抛出,析构函数不会被调用,所以我尝试在 msvc 中编译一些代码,似乎没有任何问题,但我不确定这是否只是运气。

Object::Object(){
    try{
        // Initialize multiple resources here.
    }catch(...){
        this->~Object(); // Is this safe?
        throw;
    }
}

Object::~Object(){
    // release multiple resources, if initialized.
}

【问题讨论】:

  • 我怀疑这是 UB,但我懒得做研究。拥有cleanup() 函数并从构造函数和析构函数调用它会更安全。更好的是,在 RAII 包装器中保存“多个资源”,以便它们自动释放。
  • 如果其中的任何代码抛出,我必须销毁任何剩余的资源 -- RAII 是您应该使用的,而不是显式地对析构函数进行可疑调用。这种类型的析构函数调用几乎总是为placement-new cleanup 保留。
  • 它是 UB,因为在生命周期尚未开始的对象上调用了非平凡的析构函数
  • 换句话说:{ YourResource r1; YourOtherResource r2; } 如果r1r2 在右花括号后不能自动清理,那么重新考虑你是如何编写你的类的(或者如前所述, 将这些类型包装到我展示的代码可以正常工作的类中)。

标签: c++ constructor try-catch object-lifetime explicit-destructor-call


【解决方案1】:

尽管析构函数看起来像普通的方法,并且显式的析构语法看起来像对该方法的调用,但它实际上并不只是调用该方法。在其他特定于实现的事情中,它还调用基类和数据成员的析构函数。从构造函数中抛出异常也会导致调用所有这些析构函数。因此,~Object() 后跟 throw 会调用它们两次,可能会带来灾难性的后果。

正如有人在评论中建议的那样,只需将清理代码移至普通方法即可。

构造临时函数的函数调用语法以及new/deleteoperator new/operator delete 存在类似的语法问题。它们中没有一个只是调用具有相同名称的函数,即使看起来它们应该这样做。

【讨论】:

    【解决方案2】:

    首先,invoking a member function here is fine

    成员函数,包括虚函数([class.virtual]),可以在构造或销毁([class.base.init])期间调用。

    (您的构造函数已开始执行。)

    但是 this 专门关于析构函数:

    一旦为对象调用析构函数,该对象就不再存在;如果为生命周期已结束的对象([basic.life])调用析构函数,则行为未定义。 [ 示例:如果自动对象的析构函数被显式调用,并且随后以通常会调用对象的隐式销毁的方式留下块,则行为未定义。 — 结束示例 ]

    因此,尽管我们确实知道您的析构函数不会被“再次”隐式调用,但问题是随后的重新抛出是否会导致对象“再次”被破坏的场景,如本文所述。

    在这一点上,我实际上放弃了标准语,我想知道这是否有点未指定。我的观点是,这本身可能足以避免这种善意的模式,只需将您的清理工作放在一个不错的私有成员函数中,以便在您的 catch 块和您的析构函数之间共享。

    【讨论】:

      猜你喜欢
      • 2016-06-12
      • 2015-07-27
      • 1970-01-01
      • 1970-01-01
      • 2011-04-16
      • 2014-12-18
      • 2017-02-08
      • 2013-07-13
      相关资源
      最近更新 更多