【问题标题】:What happens to base class destructor if a derived class destructor throws an exception如果派生类析构函数抛出异常,基类析构函数会发生什么
【发布时间】:2011-05-29 08:32:06
【问题描述】:

刚好发生在我身上,我想知道在以下情况下如何释放资源。

class Base {
  Resource *r;

public:
  Base() { /* ... */ }
  ~Base() {
    delete r; 
  }
};

class Derived : public Base {
public:
  Derived() { /* ... */ }
  ~Derived() {
    /* Suddenly something here throws! */
  }
};

int main() {
  try {
    Derived d;
  } catch(...) {
    /* what happened with Base::r !? */
  }
}

如果派生类析构函数抛出,会调用基类析构函数吗?还是会漏水?

【问题讨论】:

  • 根据记忆,如果析构函数抛出,标准的定义非常复杂且通常很糟糕,以至于没有人从析构函数中抛出异常。我也很确定所有标准容器都假定析构函数是 nothrow。基本上,虽然我认为从技术上讲,它是合法且明确的,但现实情况是没有人这样做,而且有充分的理由,所以永远不要这样做。
  • 您那里的样本应该没有问题。 d 只是以正常的方式超出范围。如果 d 被破坏的原因是堆栈作为处理另一个异常的一部分正在展开,事情可能会变得令人讨厌。正是这种可能性导致了永远不要抛出析构函数的经验法则。

标签: c++ exception destructor memory-leaks


【解决方案1】:

根据 §15.2/2:

部分构造或部分销毁的对象将为其所有完全构造的子对象执行析构函数,即构造函数已完成执行但析构函数尚未开始执行的子对象。

所以应该调用基类的析构函数。也就是说,就像我们知道这将清理基类一样:

#include <iostream>

struct foo
{
    ~foo()
    {
        std::cout << "clean" << std::endl;
    }
};

struct bar : foo
{
    bar()
    { // foo is initialized...
        throw 0; // ...so its destructor is run
    }
};

int main()
{
    try
    {
        bar b;
    }
    catch (...)
    {
        std::cerr << "caught" << std::endl;
    }
}

这将清理成员:

#include <iostream>

struct foo
{
    ~foo()
    {
        std::cout << "clean" << std::endl;
    }
};

struct bar
{
    ~bar()
    { // f has been initialized...
        throw 0; // ...so its destructor will run
    }

    foo f;
};

int main()
{
    try
    {
        bar b;
    }
    catch (...)
    {
        std::cerr << "caught" << std::endl;
    }
}

这也将清理基类:

#include <iostream>

struct foo
{
    ~foo()
    {
        std::cout << "clean" << std::endl;
    }
};

struct bar : foo
{
    ~bar()
    { // foo has been initialized...
        throw 0; // ...so its destructor will run
    }
};

int main()
{
    try
    {
        bar b;
    }
    catch (...)
    {
        std::cerr << "caught" << std::endl;
    }
}

这是我对这句话的理解。

【讨论】:

  • 在 VC++ 9 中确实调用了基本析构函数。
  • @sharp:在 MSVC10 中。 @Kirill:谢谢,我生疏了。 :)
  • 感谢您的回答。我认为这是正确的!
  • @wqking,我认为编译器需要在析构函数调用周围添加一个 try...catch,以便能够调用 std::terminate 以防异常逃脱析构函数而另一个异常是目前活跃。虽然不确定细节。
  • int main() { Derived d; throw 0; } 的情况下,大概没有调用基本析构函数,因为立即调用terminate() 的规则破坏了所需的析构函数调用顺序?
【解决方案2】:

确实调用了基类析构函数。示例代码:

#include 
#include 

class Base {
public:
  Base() { /* ... */ }
  ~Base() {
    printf("Base\n");
  }
};

class Derived : public Base {
public:
  Derived() { /* ... */ }
  ~Derived() {
    printf("Derived\n");
    throw 1;
  }
};

int main() {
  try {
    Derived d;
  } catch(...) {
    printf("CAUGHT!\n");
  }
}

打印出来:

Derived
Base
CAUGHT!

【讨论】:

  • 好。但 GMan 之前回答过。
  • @Alexey - 这不是一场比赛,重要的是答案的质量。
  • @Brian:不要自吹自擂,但由于我的答案已经存在,所以没有什么可以直接回答这个问题的了。可以说这个答案更糟,因为它只是证明某些编译器在这种特定条件下产生了特定输出,而不是证明它得到保证。
  • @GMan - 我的评论是针对 Alexey 的评论,而不是针对此答案或您的。既然你提出来了,我同意你说的。我只是不喜欢这样的看法,即第一个答案总是你投票赞成的那个。
  • @Brian:明白了。我认为阿列克谢想到了我所说的,只是没有说出来。
【解决方案3】:

将调用基础析构函数。

在 Effective C++ 中,Meyers 建议异常不应离开析构函数。 在析构函数中捕获异常并处理、吞下或终止。

【讨论】:

  • 您的索赔有理由吗?
  • 你写“我不认为”。我确实认为。什么? OP 的声誉得分很高,显然希望得到更明确的答案。
  • 我写了“我不认为”,因为这是我所理解的。我现在已经检查过了,是的,确实调用了基本析构函数。在异常之前运行了多少派生类析构函数仍然存在问题。
  • 问题是I thinkI don't think 不是答案。链接到迈耶斯也不是答案。这只是一个建议。 OP 想知道Will the base class destructor be called? 为什么很明显?
  • @Alexey Malistov - 不要贬低或赞美任何人。不要假设高分代表专家或低分代表傻瓜。
猜你喜欢
  • 2015-07-18
  • 2016-12-17
  • 2014-08-08
  • 2014-05-17
  • 1970-01-01
  • 2022-07-01
  • 2011-05-20
  • 2021-03-20
  • 2012-05-10
相关资源
最近更新 更多