【问题标题】:c++ ctor and dtor don't work the same manner [duplicate]c ++ ctor和dtor的工作方式不同[重复]
【发布时间】:2014-05-21 18:57:47
【问题描述】:

我正在尝试找出 C++ 中类继承的技巧,并且我已经构建了一个示例项目:

#include "stdafx.h"
#include <iostream>
using namespace std;

class A
{
public:
    A()
    {
        cout << "Class A initialized" << endl;
    }

    ~A()
    {
        cout << "Class A destructed" << endl;
    }
};

class B : public A
{
public:
    B()
    {
        cout << "Class B initialized" << endl;
    }

    ~B()
    {
        cout << "Class B destructed" << endl;
    }
};


int _tmain(int argc, _TCHAR* argv[])
{
    cout << "A* a = new A()" << endl;
    A* a = new A();
    cout << "B* b = new B ()" << endl;
    B* b = new B ();
    cout << "A* ab = new B()" << endl;
    A* ab = new B();

    cout << "delete a" << endl;
    delete a;
    cout << "delete b" << endl;
    delete b;
    cout << "delete ab" << endl;
    delete ab;

    int i;
    cin >> i;

    return 0;
}

我得到的输出是:

A* a = new A()
Class A initialized
B* b = new B ()
Class A initialized
Class B initialized
A* ab = new B()
Class A initialized
Class B initialized
delete a
Class A destructed
delete b
Class B destructed
Class A destructed
delete ab
Class A destructed

我可以将 B 类的行为理解为派生类——首先它构造基类,然后构造派生类。当它调用析构函数时,它以相反的方式完成工作。似乎合乎逻辑。

我无法理解的是 ab 的行为(我将 B 分配到 A 指针中), 为什么构造函数和纯B一样,而析构函数只在A上运行?

谢谢。

【问题讨论】:

  • 因为你还没有制作A的析构函数virtual
  • 你是对的。我的错。谢谢。

标签: c++ inheritance


【解决方案1】:

编译器调用与指针的静态类型对应的类的成员函数。指针ab的类型是A *所以编译器调用A类的析构函数。如果你将析构函数声明为虚拟的,例如

class A
{
public:
    //...
    virtual ~A()
    {
        cout << "Class A destructed" << endl;
    }
};

然后编译器将使用虚拟函数指针表。在这种情况下,在删除 ab 的情况下,表将包含指向派生类的析构函数的指针。

至于构造函数,那么当你使用 operator new B() 时,表达式中使用的静态类型是 B。所以 B 的构造函数与 A 的构造函数一起被调用,作为基类的构造函数。

【讨论】:

  • 这个答案其实是不正确的;析构函数遵循的规则略有不同,事实上,他的程序有未定义的行为。
  • 重读时:这个答案实际上是错误的(任何没有指出未定义行为的答案也是如此)。
  • 没有。这是未定义的行为。这就是为什么我说你的答案是错误的。见§5.3.5/3:“[...]如果要删除的对象的静态类型与其动态类型不同,则静态类型应是要删除的对象的动态类型的基类,并且静态类型应具有虚拟析构函数,否则行为未定义。”在这种情况下程序崩溃,或者内存被破坏,导致以后崩溃的情况并不罕见。
  • 哪个标准?我直接引用了 C++11,其中 is 说这是未定义的行为。我引用的文本在标准的早期版本中是相同的。我引用的文字清晰准确;我看不出你有什么不明白的地方。
  • @James Kanze 该标准毫无疑问地表明,在这种情况下,将调用基类的析构函数。这是这种调用的结果具有未定义的行为。这是不同的事情。并且线程的作者问为什么会被称为基类析构函数而不是派生类析构函数。所以除了你之外一切都很清楚,因为你甚至不明白你读到的东西。
【解决方案2】:

构造函数和析构函数有本质的区别 (或构造函数和任何其他函数,就此而言):当 构造一个对象,你必须在源代码中指定它的确切类型 代码。对于所有其他函数(包括析构函数),它是 只要满足一定的条件,就可以只提一个基地。 这些条件之一是函数(或析构函数)是 基类中的虚拟。

在析构函数的情况下,还有一个额外的约束,因为 析构函数涉及delete,这反过来又需要 完整对象的地址,以便正确释放内存。 因此,给定A* pA;,像pA-&gt;f() 这样的表达式将调用 基类中的函数f如果不是虚拟的,而是函数 f()在派生类中如果是虚拟的,派生类中 覆盖它。另一方面,delete pA; 将调用析构函数 如果基中的析构函数是虚拟的,则派生类的 未定义的行为 如果 pA 指向派生类,并且 基础中的析构函数不是虚拟的。不存在公正的问题 9调用基类的析构函数;虽然这可能是 简单情况下的实际行为,所有情况下的行为都是未定义的。

因此,经常建议如果一个类是 设计用作基类,析构函数应该是 虚拟的或受保护的。恕我直言,这取决于班级:如果有人 误解了std::exception&lt;&gt;,以至于写了这样的东西:

std::exception<...>* pIter = new std::vector<...>::iterator;
//  ...
delete pIter;

没有希望,也不值得费心定义析构函数 对于std::iterator,只是为了使其受到保护(在 C++11 之前的版本中,使 派生的迭代器不可能是 POD)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-10-10
    • 1970-01-01
    • 1970-01-01
    • 2021-08-16
    • 1970-01-01
    • 2021-06-15
    • 1970-01-01
    • 2019-08-09
    相关资源
    最近更新 更多