C++析构函数问题

4

关于下面的示例代码,为什么基类的析构函数会被调用两次?

class Base {
public:
    Base() {
        std::cout << "Base::Base()" << std::endl;
    }

    ~Base() {
        std::cout << "Base::~Base()" << std::endl;
    }
};

class Derived : public Base {
public:
    Derived() {
        std::cout << "Derived::Derived()" << std::endl;
    }

    ~Derived() {
        std::cout << "Derived::~Derived()" << std::endl;
    }
};

int main() {
    Base a = Derived();
    return EXIT_SUCCESS;
}

以下是运行程序时的输出示例:
Base::Base()
Derived::Derived()
Derived::~Derived()
Base::~Base()
Base::~Base()

修复了你代码的格式。这是我第一次见到So把格式搞得那么糟糕。 ;) - jalf
2
为了更好地了解发生了什么,为基类添加一个拷贝构造函数。然后你将看到与不匹配的析构函数相关的缺失构造。 - Martin York
是的,好的建议。在测试这样的东西时,一定要确保跟踪所有构造函数(加上赋值运算符)。否则你可能会错过一半的事情。 - jalf
即使是Base const&a = Derived();也不需要虚析构函数来调用~Derived,那么也不会发生切片:http://herbsutter.wordpress.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/ - Johannes Schaub - litb
8个回答

16
所发生的事被称为切片。您使用类型为Derived的对象初始化一个Base类型的对象。由于任何类型为Derived的对象都包含一个类型为Base的对象(称为“基类子对象”),因此在整个程序中将存在两个Base对象和一个Derived对象。派生对象(以及其类型为Base的基类子对象)仅在初始化时存在,而其余的Base对象存在到main结束。
由于存在两个Base对象和一个Derived对象,因此还会运行一个Base析构函数。

哦,我完全没有想到切片,只想到了非虚析构函数。很好的发现。 - jalf
没错。编译器合成的复制构造函数被用于使用派生类对象初始化基类对象,当然也会发生对象切片。 - Harish H

7
正在使用复制构造函数。如果您想查看正在发生的情况,请对复制构造函数进行工具化处理:
 Base( const Base & ) {
        std::cout << "Base::Base( const Base &)" << std::endl;
    }

同样的,Derived也是如此。

请注意,这与析构函数不是虚函数无关。


此外,还要输出this指针,以证明存在两个对象(即对象切片)。 - dave4420
Neil,就是这样。编译器正在合成一个拷贝构造函数并用于初始化基类对象。当基类对象超出作用域时,析构函数被调用两次。 - Harish H
对象切片也会发生,因为基类复制构造函数正在获取派生对象。 - Harish H

4

当你在main()函数中调用Derived()时,它会创建一个临时对象,然后将其复制到对象a中。因此,由于存在两个对象,析构函数会被调用两次。另外,正如其他人指出的那样,你的基类析构函数应该是虚拟的。


2
因为您在复制构造函数 a 之前创建了一个类型为 Derived 的临时对象。所以基本上会发生以下情况:
Derived d(); // Your temporary of type Derived is created
Base a(d); // The temporary is used to call a's copy constructor 
d.Derived::~Derived(); // The temporary is destroyed, calling both ~Derived and ~Base
a.Base::~Base(); // The nonvirtual destructor on a is called, so ~Base is called, but not ~Derived

除了编译器可能会优化掉的不必要复制之外,实际上错误在于 ~Base 不是虚拟的。

编辑 糟糕,完全忽略了像 litb 指出的切片。请阅读他的答案 :)


1
在程序中加入以下内容将使其更加清晰:
 Base(const Base& base){
        std::cout << "Base::Base(const Base& base)" << std::endl;
 }

编译器会自动为您创建一个复制构造函数。通过自己定义它(并添加打印功能),您可以看到构造函数和析构函数的数量匹配。
Base::Base()
Derived::Derived()
Base::Base(const Base& base)
Derived::~Derived()
Base::~Base()
Base::~Base()

0

2
错误。这与提问者所看到的行为无关。只有在通过基指针删除派生类的对象时才需要虚析构函数,而这里并非如此。 - anon

0

你有一个堆栈变量和一个临时变量 - 一共构造了两个对象 - 所以逻辑上析构函数会被调用两次。


0

1) 建立Derived类型的临时对象(调用Derived::Derived()和Base::Base())

2) 将临时对象复制到“a”中

3) 销毁临时对象(调用Derived::~Derived()和Base::~Base())

4) 返回EXIT_SUCCESS;

5) 销毁“a”,因此调用Base::~Base()


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接