关于继承:C++CtoR和Dor不工作相同的方式

c++ ctor and dtor don't work the same manner

本问题已经有最佳答案,请猛点这里访问。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#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;
}

我得到的输出是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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作为派生类的行为——首先它构造基类,然后是派生类。当它调用析构函数时,它的工作方式与之相反。似乎合乎逻辑。

我不明白的是a b的行为(b的分配,我把它放在a指针上)。为什么构造函数的行为与纯b相同,但析构函数只在a上运行?

谢谢。


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

1
2
3
4
5
6
7
8
9
class A
{
public:
    //...
    virtual ~A()
    {
        cout <<"Class A destructed" << endl;
    }
};

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

对于构造函数,则当使用operator new b()时,表达式中使用的静态类型为b。因此,B的constructor与a的构造函数一起调用,作为基类的构造函数。


构造函数和析构函数之间有一个根本的区别(或构造器和其他功能):何时构造对象时,必须在源中指定其确切类型代码。对于所有其他函数(包括析构函数),它是只要满足一定的条件,就可以只提到一个基地。其中一个条件是函数(或析构函数)是在基类中是虚拟的。

对于析构函数,有一个额外的约束,因为析构函数与delete有关,而delete又要求完整对象的地址,以便正确释放内存。因此,在给定A* pA;的情况下,像pA->f()这样的表达式将调用如果不是虚拟的,则为基类中的函数f,但函数如果派生类是虚拟的,则派生类中的f(),以及派生类超越它。另一方面,delete pA;将调用析构函数如果基中的析构函数是虚拟的,但却是如果pA指向派生类,并且基中的析构函数不是虚拟的。不存在公正的问题9调用基类的析构函数;尽管这可能是实际行为在简单情况下,行为在所有情况下都是未定义的。

出于这个原因,通常建议设计为用作基类,析构函数应为虚拟或受保护。不知道,这取决于班级:如果有人错误地使用了std::exception<>到了写作的地步,比如:

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

没有希望,也不值得费心定义一个析构函数对于std::iterator来说,只是为了保护它(在pre-c++11中,派生迭代器不可能是pod)。