看下面这段代码:
1 class Foo 2 { 3 public: 4 int val; 5 Foo *pnext; 6 }; 7 8 void foo_bar() 9 { 10 Foo bar; 11 if(bar.val || bar.pnext) 12 //do something.... 13 };
在C++ARM中作者说道:“C++构造函数,在它需要时被编译器产生出来”;
这里涉及到2个概念:
1. 需要的时候;
2. 编译器
当然,我们编写程序时非常希望对象Foo拥有一个构造函数,自动将val和*pnext进行初始化。这和刚才的“需要的时候”是一个概念吗?
no~前者是一个程序上的概念,而后者,则是编译器上的概念。
什么时候才能自动创建构造函数呢?——编译器需要它的时候! 即使真的编译器需要它的时候,也不会初始化成员值为0。
这里强调,如果用户没有定义构造,那么编译器100%会提供一个默认构造。但这个默认构造是“有用构造(nontrival)”还是“无用构造(trivial)”?看情况而定。
无用构造内部无参,也不会自动初始化,当然这个操作由编译器完成,只是用来生成对象。
以下我们讨论的,都是在某些情况下,编译器创建了“有用构造”。
以下4种情况,编译器立功:
1. 带有“默认构造函数”的成员对象
如果一个类没有构造函数,但内部有一个对象成员,该成员类有默认构造,那么这个类的默认构造将会由编译器合成。
该合成操作只有在构造函数真正需要被调用时才发生。
那么在C++不同的编译模块中,编译器如何避免合成出多个默认构造:
1: class Foo
2: {
3: public:
4: Foo();
5: Foo(int);
6: };
7:
8: class Bar
9: {
10: public:
11: Foo foo;
12: char *str;
13: }
14:
15: void foo_bar()
16: {
17: Bar *bar; //Bar::foo must init here
18: if(str)
19: //do something....
20: }
被合成的Bar类的默认构造内部有必要代码,用来调用Foo类默认构造来处理成员对象Bar::foo,但它并不产生代码来初始化Bar::str。
是的,将Bar::foo初始化是编译器的责任。因为Foo都有自己的默认构造了,还需要我们来多此一举吗?但将Bar::str初始化则是程序的责任。
1: inline Bar::Bar()
2: {
3: //被合成的默认构造可能会这样
4: foo.Foo::Foo();
5: }
但要注意,这个默认构造只是满足编译器的需要。
※如果类A内部含有1个或1个以上的成员对象,那么A的每一个构造都必须调用每一个成员对象的默认构造。
所以,就算你的代码是这样的:
1: Bar::Bar()
2: {
3: str = 0;
4: }
1: Bar::Bar()
2: {
3: foo.Foo::Foo(); //编译器自动加入的编译器代码
4: str = 0;
5: }
当然,如果成员对象很多,那么就按照声明次序由编译器依次添加构造调用。
2. 带有“默认构造函数”的基类
类似的道理,如果一个无任何构造的派生类继承于有默认构造的基类,那么这个派生类的默认构造由编译器生成。
1: class Base
2: {
3: Base();
4: // other member
5: }
6:
7: class A : public Base
8: {
9: // other member
10: }
它将调用基类的默认构造来合成自己的默认构造。
伪代码或许如下:
1: A::A()
2: {
3: Base::Base();
4: // other something....
5: }
3. 带有“一个虚函数”的类
1. class声明或继承一个virtual function
2. class派生自一个继承串链,其中有一个或更多的virtual base classes
此时,该类也需要自动合成出默认构造,该操作由编译器完成。
1: class Widget
2: {
3: virtual void flip();
4: //....
5: }
6:
7: void flip(const Widget &widget) {widget.flip();};
8:
9: //假设Bell和Whistle都派生自Widget类
10: void foo()
11: {
12: Bell b;
13: Whistle w;
14:
15: flip(b);
16: flip(w);
17: }
类Widget/Bell/Whistle在编译期间会发生的操作:
1. 一个virtual function table(vtbl)会在编译器产生出来,内放class的virtual function地址;
2. 在每一个class object中,一个额外的pointer member(vptr)会被编译器合成出来,内含相关的class vtbl地址;
这里,因为Widget &widget使用了引用操作,所以产生了多态。内部使用了widget的vptr和vtbl中的flip()条目,最终指向的对象为Bell或Whistle。
4. 带有“一个虚基类”的类
虚基类的实现方法在不同编译器有极大差异。
1: class X { public: int i; };
2: class A : public virtual X { public: int j; };
3: class B : public virtual X { public: double d; };
4: class C : public A, public B { public: int k; };
5:
6: //无法在编译时期确定出实际类型
7: void foo(const A* pa) {pa->i = 1024; };
8:
9: main()
10: {
11: foo(new A);
12: foo(new C);
13: };