关于默认构造

看下面这段代码:

 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:  };
posted @ 2014-04-08 14:09  大卫酱_David  阅读(236)  评论(0编辑  收藏  举报