继承中调用构造函数/析构函数的顺序
Posted
技术标签:
【中文标题】继承中调用构造函数/析构函数的顺序【英文标题】:Order of calling constructors/destructors in inheritance 【发布时间】:2011-11-24 06:05:46 【问题描述】:关于创建对象的一个小问题。假设我有这两个类:
struct A
A()cout << "A() C-tor" << endl;
~A()cout << "~A() D-tor" << endl;
;
struct B : public A
B()cout << "B() C-tor" << endl;
~B()cout << "~B() D-tor" << endl;
A a;
;
在main中我创建了一个B
的实例:
int main()
B b;
请注意,B
派生自 A
,并且还有一个类型为 A
的字段。
我正在尝试找出规则。我知道在构造对象时首先调用它的父构造函数,反之亦然。
字段(在这种情况下为A a;
)呢?当B
被创建时,什么时候会调用A
的构造函数?我还没有定义初始化列表,是否有某种默认列表?如果没有默认列表?还有关于破坏的同样问题。
【问题讨论】:
如果您的析构函数消息与构造函数消息不同,您的示例可能更易于解释。还有,那些std::sort
在做什么?
另外,在实验时,比较B b
、B* b = new B(); delete b;
和A* a = new b(); delete a;
的构造和销毁(比较当你使用virtual
关键字作为你的析构函数时会发生什么,即virtual ~A() cout<<"A D-tor"<<endl;
)
@Tom,你是对的。删除编译器错误。
我已经编辑了您问题中的代码,以便这四种方法都打印不同的消息。现在,您自己要做的就是实例化 B 并观察标准输出。如果这不是您真正的意思,请查看并回滚。
【参考方案1】:
构造总是从基础class
开始。如果有多个基础class
es,则从最左边的基础开始构建。 (旁注:如果有 virtual
继承,则优先级更高)。
然后构建成员字段。它们在
声明它们的顺序
最后,class
本身就构建好了
析构函数的顺序正好相反
不管初始化列表如何,调用顺序都是这样的:
-
基
class A
的构造函数
将构造 class B
的名为 a
的字段(类型为 class A
)
派生class B
的构造函数
【讨论】:
At the last the class itself is constructed
- 你是在说构造函数体吗?
@Wolf:我猜是的。
@Wolf。指派生类【参考方案2】:
假设没有虚拟/多重继承(这使事情变得相当复杂),那么规则很简单:
-
对象内存已分配
基类的构造函数被执行,以大多数派生结束
成员初始化被执行
对象成为其类的真实实例
构造器代码被执行
要记住的重要一点是,在第 4 步之前,对象还不是其类的实例,因为它仅在构造函数开始执行后才获得此名称。这意味着如果在成员的构造函数期间抛出异常,则不会执行对象的析构函数,而只会销毁已构造的部分(例如成员或基类)。这也意味着,如果在成员或基类的构造函数中调用对象的任何虚拟成员函数,则调用的实现将是基类,而不是派生类。 要记住的另一件重要的事情是,在初始化列表中列出的成员将按照它们在类中声明的顺序构造,而不是按照它们在初始化列表中出现的顺序(幸运的是,如果您列出成员,大多数体面的编译器都会发出警告与类声明的顺序不同)。
还要注意,即使在构造函数代码的执行过程中this
对象已经获得了它的最终类(例如,关于虚拟调度),除非构造函数完成,否则不会调用该类的析构函数它的执行。只有当构造函数完成执行时,对象实例才是实例中真正的一等公民……在此之前只是一个“想要的实例”(尽管有正确的类)。
析构以完全相反的顺序发生:首先执行对象析构函数,然后它失去其类(即从此时开始,对象被视为基对象)然后所有成员以相反的声明顺序被销毁,最后是基类销毁过程执行到最抽象的父级。至于构造函数,如果您在基析构函数或成员析构函数中调用对象的任何虚拟成员函数(直接或间接),则执行的实现将是父函数,因为当类析构函数完成时,对象失去了其类标题。
【讨论】:
@Wolf:是的,这是有保证的。如果你有派生自B
的类D
,那么在实例化D
时,首先执行B
的构造函数(最抽象的),然后执行构造函数D
。实际上,只有在所有基础和所有其他成员的构造完成后,对象才成为真正的D
(关于虚方法)(这是在构造其中一个过程中调用D
的虚方法的棘手点成员或在构建基础子对象期间)。
我要求的不是从基础到派生的构造顺序,我认为是 abstract
这个词具有误导性。据我所知,抽象类是至少具有纯虚拟方法的类。请看this example
@Wolf:当然,当术语有不同的解释时,很难讨论(“当我使用一个词时,”Humpty Dumpty 用一种相当轻蔑的语气说,“它意味着我选择它意思是——不多也不少。”):-)。然而,如果你有A <= B <= C <= D
(X <= Y
的意思是“X
是Y
”的基础),那么A
是“最抽象的”而D
是“最具体的”,这是非常流行的说法" 类,独立于纯虚方法。无论如何,我将使用“最衍生”来改写。【参考方案3】:
基类总是在数据成员之前构建。数据成员按照它们在类中声明的顺序构造。此顺序与初始化列表无关。当一个数据成员被初始化时,它会在你的初始化列表中查找参数,如果没有匹配则调用默认构造函数。数据成员的析构函数总是以相反的顺序调用。
【讨论】:
【参考方案4】:基类构造函数总是先执行。所以当你写一个语句B b;
时,首先调用A
的构造函数,然后是B
类构造函数。因此构造函数的输出将按如下顺序排列:
A() C-tor
A() C-tor
B() C-tor
【讨论】:
【参考方案5】:#include<iostream>
class A
public:
A(int n=2): m_i(n)
// std::cout<<"Base Constructed with m_i "<<m_i<<std::endl;
~A()
// std::cout<<"Base Destructed with m_i"<<m_i<<std::endl;
std::cout<<m_i;
protected:
int m_i;
;
class B: public A
public:
B(int n ): m_a1(m_i + 1), m_a2(n)
//std::cout<<"Derived Constructed with m_i "<<m_i<<std::endl;
~B()
// std::cout<<"Derived Destructed with m_i"<<m_i<<std::endl;
std::cout<<m_i;//2
--m_i;
private:
A m_a1;//3
A m_a2;//5
;
int main()
B b(5);
std::cout <<std::endl;
return 0;
本例的答案是 2531。这里如何调用构造函数:
-
B::A(int n=2) 构造函数被调用
B::B(5) 构造函数被调用
B.m_A1::A(3) 被调用
B.m_A2::A(5) 被调用
同路析构函数调用:
-
B::~B() 被调用。即 m_i = 2,在 A 中将 m_i 递减为 1。
B.m_A2::~A() 被调用。 m_i = 5
B.m_A1::~A() 被调用。 m_i = 3
4 B::~A() 被调用。,m_i = 1
在本例中,m_A1 & m_A2 的构造与初始化列表的顺序无关,而与它们的声明顺序无关。
【讨论】:
【参考方案6】:修改后的代码输出为:
A() C-tor
A() C-tor
B() C-tor
~B() D-tor
~A() D-tor
~A() D-tor
【讨论】:
以上是关于继承中调用构造函数/析构函数的顺序的主要内容,如果未能解决你的问题,请参考以下文章