C++继承的构造顺序

class A
{
public:
A(){ cout << "A::A()" << endl;}
~A(){ cout << "A::~A()" << endl;}
};
class B
{
public:
B(){ cout << "B::B()" << endl;}
~B(){ cout << "B::~B()" << endl;}
};
class C:public B
{
A a;
public:
C(){cout << "C::C()" << endl;}
~C(){cout << "C::~C()" << endl;}
};
A a;
int main()
{
C c;
}

结果是:
A::A()
B::B()
A::A()
C::C()
C::~C()
A::~A()
B::~B()
A::~A()

这是为什么?
第一个A是因为全局对象a在运行程序之前就创建了?
中间的BAC是为什么呢?
基类B派生出C,不应该是先构造出C才构造局部对象a,顺序是BCA吗?
难道局部对象在函数内的优先级是高于一切的?甚至是自身类的构造函数?

1.构造函数
先看下面的类定义
class
B1 class B2
{ {
public: public:
int
i; int i;
B1() { i = 0;
} B2() { i = 0; }
virtual void f()
{} virtual void f() {}
}
; } ;

class
M1 class M2
{ {
public: public:
int
i; int i;
M1() { i = 0;
} M2() { i = 0; }
virtual void mf()
{} virtual void mf() {}
}
; };

class C : public B1, public B2
{
public:
virtual void f() {}
M1 m1;
M2 m2;
};

(1)编译器会不会为C生成默认构造(default ctor)
编译器只在需要时才会为一个类生成def
ctor。“需要时”指:
a.一个类有虚函数
b.一个类有虚基类
c.一个类的基类有def ctor
d.一个类的成员类有def ctor
在这里,class C符合c,d两个条件,编译器会为它生成def
ctor.

(2)默认构造的内容
编译器生成的def ctor是这样的
C::C()
{
B1::B1()
B2::B2()
设定虚表指针指向C的虚表
m1::M1()
m2::M2()
}
a.按声明顺序构造基类
b.设定虚表指针
c.按声明顺序构造成员类

(3)对自定义构造函数的改造
对于B1,B2,M1,M2,已有构造函数,但编译器会对其改造,加入一些代码,完成必要的初始化工作。改造后的ctor如下:(以B1为例)
B1::B1()
{
设定虚表指针指向B1的虚表
i = 0;
}
a.设定虚表指针
b.如果用户写有代码(如i=0),则执行这些代码

(4)综合(2),(3),构造函数完整代码如下
T::T()
{
按声明顺序构造基类
设定虚表指针
按声明顺序构造成员类
如果用户写有代码,则执行这些代码
}
在用户代码执行前,基类与成员类已构造完毕,虚指针已设定。

所以C的构造顺序是:B1-〉B2-〉虚指针设定-〉m1-〉m2->C自己的代码(如果有的话)
由(3)可知,每个类的ctor中,都会暂时将虚指针指向自己的虚表。调用B1::B1时,虚指针指向B1的虚表,然后在C::C中,虚指针被重新设定为C的虚表。
在继承体系中,构造是由上而下的,B1或B2构造时,C还未构造。C构造时,B1和B2已构造完成。

所以在继承体系中,一个类的构造函数执行时,该类的所有基类已构造完毕,虚指针已设定,所有成员类也已构造完毕。但该类的所有子类均未构造完成。

2.析构函数
(1)编译器会不会为C生成析构函数(dtor)
编译器只在需要时才会为一个类生成dtor。“需要时”指:
a.类的基类有析构
b.类的成员类有析构
与构造不同,少了两点,因为在一个类析构时,这是一个破坏操作(destory),既然类以已经没用了,何必再把虚指针设一下呢。

(2)析构的内容
编译器生成的析构是这样的
C::~C()
{
m2::M2()
m1::M1()
B2::B2()
B1::B1()
}
a.按声明顺序相反的顺序析构成员类
b.按声明顺序相反的顺序析构基类
这里的相反顺序是C++标准规定的要求,不同编译器都应做到,否则不符合标准(实际上编译器或多或少都有不符标准的地方,当然不是指这里所说的顺序问题)

(3)完整的析构的调用顺序
对于已有析构函数的类,编译器会对其改造,加入一些代码,完成必要的工作。改造后的dtor如下:
T::~T()
{
设定虚表指针指向T的虚表
执行用户代码
按声明顺序相反的顺序析构成员类
按声明顺序相反的顺序析构基类
}
这是完整的析构的调用顺序
在用户代码执行前,虚指针已重新设定,以便用户代码能正确执行。然后再析构成员类和基类。

所以,如果上面例子中的五个类都有析构函数的话,调用顺序是:
虚指针设定-〉C自己的代码->m2-〉m1-〉B2-〉B1
每个类的dtor中,都会暂时将虚指针指向自己的虚表。调用C::~C时,虚指针指向C的虚表,然后在B1::~B1中,虚指针被重新设定为B1的虚表。

可见,在继承体系中,析构是由下而上的,B1或B2析构时,C已被析构。C析构时,B1和B2还未被析构。
所以在继承体系中,一个类的析构函数执行时,该类的所有基类还未被析构,所有成员类也未被析构。但该类的所有子类均已析构完成。

3.虚函数
在构造或析构中调用虚函数,会怎样.如:
B1::B1()
{
f();
}
调用的是B1::f()还是C::f()
答案是B1::f()
每个类的ctor中,都会暂时将虚指针指向自己的虚表。调用B1::B1时,虚指针指向B1的虚表
所以是B1::f()
如果在C的构造函数中调用f(),那就调用C::f()
温馨提示:答案为网友推荐,仅供参考
第1个回答  2014-03-31
由于 C 承于 B ,所以C的运行要基于B,如果B没有初始好,那么C调用B继承来的数据就会出错. 由于B对于C不存在关联,单独初始B则不存在什么影响. 置于然后是A ,因为A与C不存在关联,所以单独初始A不存在影响,而先初始C的话,A则没初始完成,在构造中引用A时就会出错. 所以按顺序来就是 B A C
全局变量是在什么时候就可以使用的,如果不在局域变量之前全成初始,使用时就会出错. 最后顺序则是 A B A C
第2个回答  推荐于2016-10-22
全局变量确实是在main运行之前构造(但不能说是程序运行之前)。因为,你在任何函数中引用a时,它应该已经存在了。

BAC的顺序则是因为父类先于子类构造,成员则晚于父类,早于构造函数体。析构顺序反过来就行。本回答被提问者采纳
第3个回答  2014-03-31
A::A() //C的基类
B::B() //C的基类
A::A() //C的成员变量A
C::C() //C自己的构造函数
C::~C() //析构C自己
A::~A() //析构C的成员变量A
B::~B() //析构C的基类B
A::~A() //析构C的基类A

必须先构造了基类,才能构造子类(万一子类里调用基类的成员怎么办)。

那个全局变量A a根本就没有构造。因为从来没有使用过a。你可以调用一下a,才能够构造a对象。追问

A::A()为什么是C的基类?他们之间的继承关系在哪里啊?

追答

抱歉,看错了。嘿嘿
A::A()应该是全局变量的吧。

本回答被网友采纳
相似回答