高山流水

----- 要黑就黑彻底

Inside The C++ Object Model 学习笔记 -- The Semantics Of Constructors

2.1 Default Constructor 的构造操作
    按照C++ Annotated Reference Manaual 的说法:"Default constructor... 在需要的时候被编译器产生出来". 这里的关键字是:“在需要的时候”。
    C++ Standard[ISO-C++95]中又有如下的声明:对于class X, 如果没有任何user-declared constructor,那么会有一个default constructor 被暗中声明出来....一个被暗中声明出来的default constructor 将是一个trival的constructor"。 这里我觉得主要是“user-declared constructor”,它包括用户所定义的所有的constructor还有其它的一些有virtual关键字的操作。这样下面的四种情况下由编译器生成的constructor是notrival default constructor:
   
    1. 带有 Default Constructor 的 Member Class Object(带有缺省构造函数的类成员对象)
    如果一个类的带有一个或多个的类成员对象,而类成员对象又有构造函数,那么就会生成该类就会生成一个notrival default constructor 构造函数。 成员对象的构造函数的调用是按成员对象的声明顺序执行的。例如:

class  Foo  public : Foo(), Foo( int ) . } ;
class  Bar  public : Foo foo;  char   * str; }
  
void  foo_bar()
{
       Bar bar; 
//  Bar::foo在这里被初始化(调用Foo的缺省构造行数: Foo:Foo())
      
      
//
      if  (str)  { }   . 
   }

    
    对于有多个成员对象的类,则按成员对象在类的声明中的顺序调用它们的构造函数。
      
    2. 带有 Default Constructor 的 Base Class(带有缺省构造函数的基类对象)       指的是一个没有任何constructorsrs的类派生自一个带有“default constructor”的基类。 如果类存在多个构造函数的话,那么编译器也会扩充现有的每一个构造函数,将调用基类的default constructor的代码加入到其中。
       
    3. 带有一个 Virtual Function 的 Class
    这样的virtual Function可以使继承来的或直接定义的。 编译器在编译期间会做如下工作
    a. 生成一个virtual Function talbe(vtbl), 用来存放class 的virtual function的地址
    b. 在每一个class object中,添加一个额外的pointer member(vptr),它指向class vtbl的地址
   
    4. 带有一个 Virtual Base Class 的 Class
    这种情况是在导出类的安插一个virtual base classes的指针,用来在运行时存取virtual base class 的成员。例如:

 1     class X { publicint i; }; 
 2     class A : public virtual X   { publicint j; }; 
 3     class B : public virtual X   { publicdouble d; }; 
 4     class C : public A, public B { publicint k; }; 
 5 
 6     // cannot resolve location of pa->X::i at compile-time 
 7     void foo( const A* pa ) { pa->= 1024; } 
 8     // 在编译期可能生成如下形式:
 9     // void foo(const A* pa) { pa->_vbcX->i = 1024; } // _vbcX: virtual base class X
10     
11     main() 
12     { 
13         foo( new A ); 
14         foo( new C ); 
15     
16         //  
17     }
18 
19 

    
    注意: 在合成的default constructor中,只有base class subobjects和member class objects会被初始化,其它所有的nostatic data member都不会执行初始化的操作.要我们自己手动的去执行初始化的操作.

    其实把握一个原则:要不要合成一个构造函数,依据编译器的需要与否,合成出来的constructor也只是完成那些编译器所需求。
   
    两个错误的观点:
 i. 任何时候,如果没有定义default constructor,就会被合成一个出来。(而是根据编译器的需要决定是否生成一个deault constructor)
 ii. 编译器生成出来的default constructor会明确的初始化每类中的每一个data member.


2.2 Copy Constructor的构建操作
    有三种情况下会有一个Copy Constructor的操作:
 i.   对一个object做明确的初始化操作  // class X{ ....};   X x;    X xx = x;
 ii.  当某个object作为参数传递给某个函数  // void foo(X x) { .... }
 iii. 函数传回一个class object对象  // void foo(){ X xx; return xx;}

    1. Default Memberwise Initialization(缺省的按成员初始化)
    如果一个类没有explicit copy constructor时, class object在执行copy constructor时,内部以default member initialization的手法来完成,就是把每一个内建或者派生而来的data member的值从某个object 拷贝一份到另一个object身上,对member class object是以递归的方式调用memberwise initialization的。
   
    这种初始化是通过在必要时生成一个copy constructor或通过 bitwise copy 来完成的。 如果要通过bitwise来完成这这样的操作,那么必须表现出bitwise copy semantise. 
   
    C++ Standard中也把copy constructor分成trial 和notrivial两种类型,只有notrivial的实体才会被合成于程序中。决定一个copy constructor是否为trivial在于类是否表现出所谓的"Bitwise copy sematics".
   
    2. Bitwise Copy Semantics
    在 bitwise copy sematics 下,不会构造出一个default copy constructor. 例如:
 

 1    class Word
 2     {
 3     public
 4         Word(const char*);
 5         ~Word(){ delete []str; }
 6         //
 7     private:
 8         int cnt;
 9         char *str;
10     }  
11     以上的是一个表现出Bitwise copy sematics的类,而以下是一个没有表现出bitwise copy sematics的类
12     class Word
13     {
14     public
15         Word(const char*);
16         ~Word(){ delete []str; }
17         //
18     private:
19         int cnt;
20         String str;
21     }
22     
23     class String 
24     {
25     public
26         String(const char*);
27         String(const string&); // String的显示copy constructor
28         ~String();
29         //.
30     }
31     这种情况下会合成一个copy constructor以调用class String object的copy constructor.
32     inline Word::Word(const Word& wd)
33     {
34         str.String::String(wd.str);
35         cnt = wd.cnt;
36     }    
37 

       
    3. 不表现出Bitwise Copy Sematics的特性的情况
    以下的几种情况class不表现出 Bitwise Sematics:
        a. 当class 内含有一个member object, 而后者的class 声明有一个copy constructor时(合成或声明的都可以),就不表现出Bitwise Sematics
        b. 当class 继承自一个base class, 而后者存在有一个copy constructor(合成或声明的都可以), 就不表现出Bitwise Sematics.
        c. 当类声明了一个或多个virtual function时
        d. 当class派生自一个继承创串链,其中有一个或多个virtual base classes时, 类就不表现出Bitwise Sematics
    这四种情况和default constructor生成的是否一个trivial是一样的, 也是满足编译器的需求。
      
    4. 重新设定Virtual Table 的指针
    对于以上:当类声明了一个或多个virtual function的情况,程序会为类对象做如下两个扩张操作:
        (1) 增加一个vtbl的指针,内含virtual function的指针地址。
        (2) 在类对象内安插一个vptr指针,指向virtual function table.
    对于把一个base class object 以其derived class object的值初始化操作的时候,会发生切割(sliced)操作。这时也要保证vtbl的正确和vtpr的正确性。
   
    5. 处理Virtual Base Class Subobject
    如果一个class object是以另外一个object作为初始值的话,而后者又有一个virtual base class suboject的话,那么也会使bitwise copy sematics失效。例如:

 1     class Raccoon : public virtual ZooAnimal{
 2     public:
 3   Raccoon(){  ; }
 4   Raccoon(int val) { ; }
 5   private:
 6   // 
 7     };
 8 
 9     class RedPanda : public Raccoon{
10     public:
11   RedPanda() { .; }
12   RedPanda(int val) { .; }
13   private:
14   // 
15     };
16     执行如下操作时会要求合成一个copy constructor:
17     RedPanda little_red;
18     Raccoon little_critter = little_re;  // 这里编译器必须明确的把little_critter的virtual base class pointer/offset初始化。
19 
20     // 如果只是这样的操作用bitwise copy 就可以了
21     Raccoon rocky;
22     Raccoon little_critter = rocky;   // 用简单的bitwise copy 就可以搞定了
23 
24 


2.3 程序转化语义学(Program Transformation Semantics)
 指的是程序在执行的是候要做的类型和操作的转换。 下面例子反映了三种转换操作:

 

 1  #include "X.h"
 2 
 3  X foo( X x0)
 4  {
 5   X xx;
 6 
 7   // ., 对xx和x0进行操作
 8 
 9   return XX;
10  }
11 

 

 1. 明确的初始化操作(Explicit Initialization)
 例如:

1 X x0
2   void  foo_bar()
3   {
4   X x1(x0);
5   X x2  =  x0;
6   X x3  =  X(x0);
7  }

8

 
 对程序做两个阶段的转化:
 i. 重写每一个定义,其中的初始化操作都会被剥除
 ii. 类的Copy constructor操作会被安插进去
 转化后可能成为这样的(伪码):
 这也就是说前面三种的初始化操作都会转换成Explicit Initialization.

 1  void foo_bar()
 2  {
 3   X x1; 
 4   X x2;
 5   X x3;
 6 
 7   x1.X::X(x0);
 8   x2.X::X(x0);
 9   x3.X::X(x0);
10  }
11 
12 

 2. 参数的初始化(Argument Initialization)
 例如下面:

 1  void foo(X x0){ ; }
 2  void call()
 3  {
 4   X xx;
 5 
 6   // . xxx 初始化及其操作
 7 
 8   foo(xx);
 9  }
10  转换后的操作为:
11  void call()
12  {
13   X xx;
14 
15   X __temp0;
16   __temp0.X::X(xx);
17   foo(__temp0);                // foo 函数应变成:void foo(X& x0);
18   __temp0.X::~X();  
19  }
20 
21 

 这就是为什么当传值到孙数的时候,不会把函数内对传入值的操作结果不会被修改,因为它在函数内本来修改的就不是外部传人的那个变量。


 3. 返回值的初始化(Return Value Initialization)
 例如:
 X bar()
 {
  X xx;
  // ....
  return xx;
 } 
 
 转化为如下伪码:
 void bar(X& __result)
 {
  X xx;
  xx.X::X();   // 编译器生成的一个缺省的构造函数调用

  // 处理 ....

  __result.X::X(xx);    // 编译器生成的copy constructor调用操作
  return;
 }
 
 如下的操作转化为:
 void foo()
 {
  X xx = bar();
 }

 被转化为:
 void foo()
 {
  X xx;
  bar(xx);   // 注意这里不用执行 default constructor
 }
 
 针对这种转换,可以从使用层面上进行优化和在编译器层面上做优化操作。在编译器层面上的操作就是采用NRV(Named Return Value)技术。
 如:
 x bar()
 {
  X xx;
  // ....
  return xx;
 }
 优化为:
 void bar(X& __result)
 {
  __result.X::X();

  //....  直接的处理 __result

  //
  return ;
 } 

2.4 成员初始化队伍(Member Initialization List)
 这里有两点要注意:
 1. 以下的四种情况下必须使用 member intialization list
  I.   当初始化一个reference member时
  II.  当初始化一个const member时
  III. 当初始化一个base class 的constructor,而它拥有一组参数时
  IV.  当调用一个member class的constructor, 而它拥有一组参数时

  其它情况下可以用在构造函数内初始化也可以
 
 2. 关于初始化的次序问题:
  编译器的初始化的顺序是按member声明次序来依次的初始化的。它会安插一些代码到构造函数内,并放在任何其它用户初始化的代码之前.

 

posted on 2006-10-30 14:33 猩猩 阅读(297) 评论(0)  编辑 收藏 引用 所属分类: C&C++语言


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理