如何告诉析构函数不被调用?

时间:2015-12-11 20:42:52

标签: c++ c++11

我刚接到面试问题,面试官问了

  

如何告诉析构函数何时应该被调用?

     

如果没有调用析构函数,你会怎么做?

说实话,我不知道答案。我的猜测是将析构函数放在try catch块中,但我从未见过人们这样做。有更好的解决方案吗?

6 个答案:

答案 0 :(得分:7)

有很多方法可以无法调用对象的析构函数:

  • 致电abort_exit(偶数exit会使堆栈变量不受影响)。
  • 让构造函数抛出异常。 (从技术上讲,如果构造函数抛出,对象永远不会存在,所以没有一个对象可以调用它的析构函数。)
  • 调用未定义的行为(此时C ++标准允许任何发生)。在分配有delete的数组上调用new []是调用未定义行为的一种方法,一种常见的行为是仅调用第一个对象的析构函数(留下第二个及后续不受欢迎的行为) - 但它是&#39 ; s仍然是未定义的行为。
  • 另一种调用未定义行为的方法是,一种很可能让析构函数无法调用的方法是使用指向基类的指针实际指向派生对象,并在指向基类的指针上调用delete。如果基类没有虚析构函数,则表明存在未定义的行为。
  • 您尚未在分配有delete的指针上调用new(如果您有内存泄漏,这尤其有问题)。 (这实际上是一个特别常见的情况"析构函数不应该已经运行")。

如果您正在尝试调试程序并想知道是否正在调用析构函数,那么

  • 设置断点并在调试器下运行
  • printf或您正在使用的任何日志框架。

答案 1 :(得分:3)

这是另一个经典的无破坏:

#include <iostream>
#include <memory>

class Base
{
public:
    Base()
    {
        std::cout << "All your base, sucker!" << std::endl;
    }
    ~Base() <-- note lack of virtual
    {
        std::cout << "Base destroyed!" << std::endl;
    }
};

class Sub: public Base
{
public:
    Sub()
    {
        std::cout << "We all live in a Yellow Submarine..." << std::endl;
    }
    ~Sub()
    {
        std::cout << "Sub destroyed" << std::endl;
    }

};

int main()
{
    std::unique_ptr<Base> b(new Sub());
}

输出:

All your base, sucker!
We all live in a Yellow Submarine...
Base destroyed!

由于Base的析构函数不是虚拟的,因此在销毁时会调用~Base而不是~Sub,并且~Base不知道Sub甚至存在并且无法致电~Sub完成清理工作。

答案 2 :(得分:2)

例如,您可以在要测试的类中放置一个静态bool,在构造函数中将其设置为true,在析构函数中将其设置为false。当没有调用析构函数时,bool将保持为真。或者它可以是静态int,构造函数中的增量和析构函数中的减量(以及检查范围之前和之后的计数)。这是检查资源泄漏的简单方法之一。我已经在单元测试中使用这种技术来轻松检查当自定义智能指针超出范围时是否调用了正确的构造函数。

在许多情况下可能不会调用析构函数,通常是编程错误导致的。例如:

  • 通过基类指针删除继承的类而不使用虚拟析构函数(然后只调用基础析构函数)
  • 删除指向前向声明的类的指针(这种情况很棘手,因为只有部分编译器会发出警告)
  • 完全忘记删除(内存泄漏)
  • 通过放置new来初始化对象,而不是手动调用析构函数(这是放置new所必需的)
  • 不匹配的数组/非数组运算符(通过new []分配并通过常规删除删除 - 如果它没有崩溃,它只调用第一个项目的析构函数)

答案 3 :(得分:0)

我不知道面试官想问你什么,因为背景不明确,但以下几点可能会有所帮助

对于堆栈上的对象 - 在对象超出范围时调用析构函数。

对于在堆上创建的对象 - 对于new创建的每个对象,delete将调用析构函数。如果程序在删除之前终止,则可能不会调用析构函数,在这种情况下应该进行正确的处理(我建议使用智能指针来避免这种情况)

答案 4 :(得分:0)

以下是未调用析构函数的示例:

#include <iostream>

class A {
  public:
     ~A() { std::cout << "Destructor called" << std::endl;}
};

int main()
{
   A *a = new A;
   return 0;
}

还有很多其他例子。像铸造,静电,...

答案 5 :(得分:0)

检测“负面事件”并不容易:没有发生的事情。

相反,我们测试的是无条件发生的事件,并且总是在我们试图检测的有趣事件之后(当事件确实发生时)。当其他情况发生时,我们就知道我们已经超过了有趣的事情应该发生的时间点(如果它发生的话)。在那时,我们有理由寻找一些确定有趣事件是否发生的积极证据。

例如,我们可以让析构函数设置某种标志,或调用一些回调函数或其他。我们也知道C ++程序按顺序执行语句。因此,假设我们不知道在S1中执行语句S1 ; S2期间是否调用了给定的析构函数。我们只是在执行S1之前安排收集证据,然后在S2之内或之后,我们寻找那些证据(是标志集,是调用的回调,......)< / p>

如果这只是在调试期间,那么请使用调试器或代码覆盖工具!

如果你想知道“当我运行这样的代码时执行了这行代码”,那么会在其上放置一个调试器断点。

或运行代码覆盖率工具然后分析结果:它会告诉您程序行的次数。未执行的行将被标记为从未到达(无覆盖)。代码覆盖可以累积来自程序的多次运行的覆盖信息;它们可以帮助您找到未被测试用例命中的代码。