C++构造函数调用顺序 所引起的感想,顺便散分
刚在网上看到一篇文章,《C++构造函数调用顺序》。
发现每天都会遇到的这个小问题(C++构造函数调用顺序)还真不简单啊!
全文如下:
1.虚基类的构造函数;虚基类构造函数如果有多个,虚基类则构造函数的调用顺序是某类在类派生表中出现的顺序而不是它们在成员初始化表中的顺序;
2、创建派生类的对象,基类的构造函数函数优先被调用(也优先于派生类里的成员类);基类构造函数如果有多个,基类则构造函数的调用顺序是某类在类派生表中出现的顺序而不是它们在成员初始化表中的顺序;
3、如果类里面有成员类,成员类的构造函数优先被调用;成员类对象构造函数如果有多个成员类对象则构造函数的调用顺序是对象在类中被声明的顺序而不是它们出现在成员初始化表中的顺序;
4、派生类构造函数
作为一般规则派生类构造函数应该不能直接向一个基类数据成员赋值而是把值传递 给适当的基类构造函数否则两个类的实现变成紧耦合的(tightly coupled)将更加难于正确地修改或扩展基类的实现。(基类设计者的责任是提供一组适当的基类构造函数)。
我以前基本上只知道第2,3点,真悲剧,呵呵。
还有第4点我看得不是很明白,如果有人来解释一下就再好不过了。
[解决办法]
就是指不能在派生类构建直接对基类成员赋值, 而要调用基类的构造函数的方式,如:
Devide: base("aaaa") //传"aaaa"
{
//而不能
//str = "aaaa"
}
[解决办法]
第一条你写得有问题:要么是派生类有多个基类的时候那么基类的构造函数调用顺序按照声明的顺序。
比如:
struct Bases1{
Bases1(){}
};
struct Bases2{
Bases2(){}
};
struct Derived:public Bases1,public Bases2{
Derived(){}
};
当我们声明一个Derived obj;时,基类部分的构造顺序是 Base1,Bases2。
如果是
struct Derived:public Bases2,public Bases1{
Derived(){}
};
那么声明一个Derived obj时基类部分的构造顺序是Base2,Base1.
至于如果有虚基类,那么肯定是有最底层的派生类来调用其构造函数。
第4点,我们说构造函数的作用主要就是用来初始化。初始化的作用 就是让数据成员都有意义。所以派生类在这方面是不应该越俎代庖去给基类部分的数据成员进行赋值。而且无论如何都会先调用基类的构造函数,然后再构造派生类部分。
[解决办法]
这些可以参阅深度探索C++对象模型构造函数语义结合数据布局来理解
另外这些东西说它是语义也不太对,也不能说是规定,总之让人觉得理所当然就是这样
[解决办法]
(6) 从虚基类直接或间接派生的派生类中的构造函数的成员初始化列表中都要列出对虚基类构造函数的调用。但仅仅用建立对象的最远派生类的构造函数调用虚基类的构造函数,而该派生类的所有基类中列出的对虚基类的构造函数的调用在执行中被忽略,从而保证对虚基类子对象只初始化一次。
(7) 在一个成员初始化列表中同时出现对虚基类和非虚基类构造函数的调用时,虚基类的构造函数先于非虚基类的构造函数执行。
http://baike.baidu.com/view/1267123.htm
[解决办法]
上代码,验证第一条:
1.虚基类的构造函数;虚基类构造函数如果有多个,虚基类则构造函数的调用顺序是某类在类派生表中出现的顺序而不是它们在成员初始化表中的顺序;
#include <iostream>using namespace std;//基类class CBase{public: CBase(){cout<<"CBase()"<<endl;}};class ABase{public: ABase(){cout<<"ABase()"<<endl;}};class BBase{public: BBase(){cout<<"BBase()"<<endl;}};class DBase{public: DBase(){cout<<"DBase()"<<endl;}};//A派生于ABaseclass A : virtual public ABase{public: A(): ABase(){cout<<"A()"<<endl;}};//B派生于ABaseclass B : virtual public ABase{public: B():ABase(){cout<<"B()"<<endl;}};class D : virtual public DBase{public: D(){cout<<"D()"<<endl;}};// ABase DBase// | \ |// A B CBase D// \ | / /// \ | / /// \ C /______/////class C : virtual public CBase, virtual public B,virtual public A//class C : virtual public B, virtual public CBase,virtual public A//class C : virtual public A,virtual public CBase,virtual public B, public Dclass C : virtual public A, public D ,virtual public CBase,virtual public B{public: //虚基类构造函数如果有多个:A(), CBase(), B();虚基类则构造函数的调用顺序是C类 //在类派生表:virtual public A, virtual public CBase,virtual public B中出现的顺序 //而不是下面它们在成员初始化表中的顺序,可以通过修改注释掉的代码行验证 //注意:虚基类,与非虚基类的构造函数调用次序是:先虚基类,之后才是非虚基类的构造函数 //C():A(),B(),CBase(){cout<<"C()"<<endl;} //C():B(),A(),CBase(){cout<<"C()"<<endl;} C():CBase(),A(),B(),D(){cout<<"C()"<<endl;} };int main(){ C c; return 0;}
------解决方案--------------------
关于第四条:
假设你的基类中存在一个动态数组,而构造函数接受数组的大小,可能会这么写
class Base
{
public:
Base(int n){p = new int[n];}
int *p;
};
当你实现子类构造函数的时候不应该直接操作p,因为可能以后基类会改为以vector实现,到时候你不得不再次修改你的代码。
[解决办法]
参考:
对象内存布局 (13)
对象内存布局 (14)
对象内存布局 (15)
对象内存布局 (16)
[解决办法]