【问题标题】:Base class's destructor called without destroying the base class!基类的析构函数被调用而不破坏基类!
【发布时间】:2011-04-30 15:06:00
【问题描述】:
#include<iostream>
using namespace std;

class A
{
public:
        int i;
        A() {cout<<"A()"<<endl;}
        ~A() {cout<<"~A()"<<endl;}
};
class B:public A
{
public:
        int j;
        B(): j(10)
        {
                this->i=20;
                this->~A();
        }
};

int main()
{
        B abc;
        cout<<"i="<<abc.i<<" j="<<abc.j<<endl;
}//main

两个问题:

  1. 为什么 A 的析构函数会像普通函数一样被调用,而不是销毁对象? (或者只有当子类的析构函数调用基类的析构函数时,基类才会被销毁?)我正在尝试这个示例代码来了解析构函数是如何工作的。因此,如果简单地调用析构函数不会破坏对象,那么显然还有其他类型的调用会调用析构函数,然后才会破坏对象。这种电话有什么特别之处,又是什么电话?
  2. 有没有办法在 B 的构造函数中为 A 设置一个初始化列表?像这样的:

    class B:public A
    { 
        B(): j(10), A():i(20) {}
    };
    

【问题讨论】:

  • 你能提供没有未定义行为的代码吗,无论明显的问题是什么?例如,没有显式析构函数调用的代码?
  • -1 不要编写愚蠢的代码(即在构造函数中破坏自己的基类)
  • this-&gt;~A(); 到底是为了什么?当然你should understand他们在你使用它们之前做了什么? :)
  • @Jan 和@GMan:如果我在构建应用程序时使用相同的代码,那将是愚蠢的代码。在这里,我只是测试析构函数的工作以了解 C++ 的工作原理。我真正想知道的是析构函数是如何工作的。即:是否会调用析构函数来破坏对象。显然在这里,调用析构函数是不够的。其他事情发生在引擎盖下。我想知道那是什么。
  • @Armen:对象被销毁后使用。这本身就是UB。然后它被隐式析构函数调用第二次销毁(假设它发生在第一个 UB 中)。这也是UB。干杯,

标签: c++ inheritance destructor ctor-initializer


【解决方案1】:
  1. 析构函数就像你可以调用的任何其他普通函数(但你不应该这样做,除非你使用一个placement new)。当您在对象上调用delete 时,会发生两件事:调用析构函数进行清理,然后调用operator delete 以释放为对象分配的内存。这里没有发生第二步。

  2. 不,你不能这样称呼它。你可以做的是这样的事情:

    A 级 { 民众: A(int n) : i(n){} };

    B 类:公共 A { 民众: B() : A(20), j(10){} };

【讨论】:

  • 当您自己管理底层对象内存并使用placement new 创建对象时,可以调用析构函数。当然,这种情况极为罕见。
【解决方案2】:
  1. 基类的析构函数应该是虚拟的。在这里,因为它是在堆栈上创建的,所以没问题,但无论如何..
  2. 不可以,但是你可以在B的构造函数初始化列表中调用class A()构造函数,像这样:
    B(): A( .. ), ...

A* a = new B();
//..
delete a;

不会调用 B 的析构函数,除非 class A 析构函数是虚拟的。这就是为什么不应派生 STL 容器的原因——它们的析构函数不是虚拟的。

【讨论】:

  • 不。将基类的构造函数设为 virtual 不会破坏 A。
  • 除非子类的对象通过指向基类的指针被销毁,否则不需要虚拟析构函数。
  • 不,基类的析构函数不需要是虚拟的。但是很难说原始代码和问题是什么。呈现的代码只是具有未定义的行为。
  • @Nav - 没有人谈论构造函数,而是析构函数。并且不要在 B 的构造函数中调用 this-&gt;~A(); ... @Victor - 是的,指针是正确的。这里没有这样的问题,因为该对象不是使用 new 创建的,但仍然...... @Alf - 是的,它不需要,但它应该是,对吗?或者,在某些情况下可能存在内存泄漏。我同意未定义的行为(:
  • 我只是在测试它以了解这些东西是如何工作的。对我来说,从底层了解概念非常重要。用基本概念进行创新测试/实验教会了我很多东西。
【解决方案3】:

@Nav:不,你对“破坏”的理解是错误的。当一个对象的析构函数被调用时,这个对象就被销毁了。你似乎相信它所驻留的记忆会完全消失,但这从未发生过。对象不再存在,但是对象通常会留下一些垃圾数据,如果您愿意打破 C++ 的规则并调用未定义的行为,那么您可以读取那些剩余的字节,它们会 看起来像 对象,并且由于没有运行时检查您是否正在访问有效对象,因此您通常可以将它们视为 对象。你做什么。

这是非法的,未定义的行为,但在实践中它通常有效。

再一次,析构函数不会物理蒸发内存。执行析构函数后,您的 RAM 仍然具有相同的容量。从概念上讲,一旦析构函数运行,对象就不再存在。但它包含的数据仍然存在于内存中。

【讨论】:

  • +1 用于理解 OP 对整个“破坏”概念的问题。
【解决方案4】:

要点:

  1. 这是一个未定义的行为,但只有 ~A() 是通过类 B 的实例调用的,因为 ~A() 未声明为虚拟的。在Wikipedia 上查看更多信息。
  2. 没有。对于派生类,首先调用您的父类,然后分配参数。

关于维基百科的第 1) 点:

没有虚拟析构函数,而 删除 B 类的实例将 正确调用两个 B 的析构函数 和 A 如果对象被删除为 B的实例,B的实例 通过指向其基址的指针删除 A 类将产生 undefined 行为。

示例(针对第 2 点):

B(): A(), j(10) {}

B(): A() {j = 10;}

【讨论】:

    【解决方案5】:

    1) C++ 中的析构函数调用顺序与构造函数调用顺序相反。所以首先派生类对象被销毁,然后是基类对象。

    2) 没有。

    【讨论】:

      【解决方案6】:

      在您提供的代码中,您确实在破坏基类,因此i。调用析构函数然后使用死对象是未定义的行为——它可能会工作,也可能会崩溃。

      如果i 是比int 更复杂的东西(例如vector),尝试对它执行任何操作都可能导致崩溃。

      【讨论】:

        【解决方案7】:

        如果您自己显式调用 ~SomeClass(),则将调用析构函数。这会使对象(在本例中为对象的基类部分)处于销毁状态。

        由于析构函数不是虚函数,派生类的析构函数不会被调用,但SomeClass的基类也会被销毁。

        仅仅通过使用 i 成员来尝试找出 A 是否真的被破坏了,这不是一个好的测试。实际上,您无法对此进行测试,因为使用该对象会导致未定义的行为。它可能有效,也可能无效(在您的情况下,它可能会打印“i=20 j=10”,但 i 已经被销毁)。

        【讨论】:

          猜你喜欢
          • 2020-12-25
          • 2014-01-15
          • 1970-01-01
          • 1970-01-01
          • 2015-07-18
          • 1970-01-01
          • 2014-10-02
          • 2011-02-12
          • 1970-01-01
          相关资源
          最近更新 更多