深入探索C++对象模型读书笔记 (二)

Posted on 2010-03-03 22:23 rikisand 阅读(1368) 评论(4)  编辑 收藏 引用 所属分类: C/C++

构造函数语义学

--了解编译器在构造对象时背着我们干了什么勾当

Default Ctor 在需要的时候被构建出来~

什么需要? 是编译器需要而不是程序的需要,所以不要期望编译器生成的Ctor会帮我们把我们的成员变量初始化为零。那是程序的需要,也就是我们程序员的任务····

例如:

      class Foo(public:int val;);

      void foo_bar(){Foo bar; if(bar.val)dosth;} 不要期望编译器帮我们初始化它为0。只有global变量会初始化为0,初始化val这样的变量需要我们自己写代码的~~

Default Ctor分为trival(没用的)和non-trival的,下面讨论什么时候编译器需要ctor 也就是有用的ctor,这时候如果我们没有提供一个默认的default ctor 它会帮我们合成一个的~~

1.带有Default Ctor 的member class object

很好理解,既然内部成员含有default ctor 那么我们创建新的对象时需要调用它,而我们并木有调用它的函数,编译器自然会帮我们提供一个。如果我们提供了default ctor ,那么编译器会在我们提供的函数加入调用必须调用的member class 的default ctor。同样的在每一个ctor里面都将在用户写的代码之前调用需要调用的default ctor -------看例子:

class Dopey();class Sneezy{public:Sneezy(int val);Sneezy();};class Bashful{public:BashFul();};

class Snow_white{public: Dopey dopey;Sneezy sneezy;Bashful bashful;private:int muble};

如果Snow_white没有定义default ctor 那么编译器会创建一个,并在其中依照声明顺序依次调用dopey sneezy bashful的default ctor 然而如果:

Snow_white::Snow_white():sneezy(1024){muble=2045;}

编译器会扩张为

Snow_white::Snow_white():sneezy(1024){

  dopey.Dopey::Dopey();

  sneezy.Sneezy::Sneezy(1024);

  bashful.Bashful::Bashful();

  /////////   explicit user code

  muble = 2048;

}

2.派生自带有Default ctor 的 base class

同样道理如果父类有Default ctor 子类当然要调用,编译器会为想上面一样为我们加上

3.含有virtual functions的Class

创建object 时候需要ctor 来设置好vptr

4.带有virtual base class

virtual base 实现方法各有不同,然而共同点是必须是virtual base class 在其每一个derived class中的位置能够与执行期准备妥当

X A B C 菱形继承

void foo(const A*pa){pa->i=1024;}

foo(new A);foo(new C);

知道pa 后 i在对象中的位置并不是固定的,而是在运行时真正确定pa指向什么对象A还是C才能确定的,因此需要设定一个指向基类subobject的指针,所以需要ctor工作了

OK:

1.任何class 如果没有定义Default ctor 就会被合成一个

2.合成出来的Default ctor 会明确设定每一个data member值

错的很easy了~

-------------------------------------------------------------------------------

再来看 Copy Ctor:

copy ctor 负责用另一个对象初始化一个对象

operator = 负责用另一个对象给一个对象赋值

直接赋值时,传参时,返回时可能调用Copy ctor

Default member initialization~~~

也就是memberwise 的initialization

他会把每一个data member (内建的或者派生的)从一个object 拷贝到另一个object中去

如果object允许bitwise的拷贝那么编译器就不用生成一个nontrival的default copy ctor

什么时候不可以呢~

1 内含一个member object 而后者含有copy constructor (声明或者合成的)

2  继承一个base class 后者有copy ctor

3  含有virtual func

4  派生自一个继承链,其中有virtual base class

1和2 中编译器会把member 或者baseclass copy ctor 调用安插在合成的copy ctor 中

3 中:

如果两个同样类型的object赋值时,没有问题因为他们的vptr相同

但是考虑子类赋值给父类,此时vptr需要更改,那么此时不具有bitwise特性,因此需要编译器来加入语句正确更新vptr

4中:

每个编译器都承诺必须让derived class 中的virtual base class object 在执行期间准备妥当,维护位置完整性是编译器的责任。bitwise copy 有可能会破坏这个位置所以编译器需要在自己合成的copy ctor 中作出仲裁

同样问题发生在继承体系中子类向父类赋值时,由于对象模型问题,直接bitwise复制可能会导致base class object 的破坏(后面章节会有讨论)

--------------------------------------------------------------------------------

程序转化语义学:

X x0;

void foo(){X x1(x0); X x2=x0; X x3=X(x0);}

转化:重写定义,初始化操作会被剥除   copy constructor 调用会被安插

void foo(){ X x1;X x2; X x3;  x1.X::X(x0); x2.X::X(x0); x3.X::X(x0);}

 

参数的初始化:

一种策略导入暂时的object

void foo(X x0);X xx; foo(xx);

转化:

X _tmp;

_tmp.X::X(x0); foo(_tmp);

foo变成 void foo(X& x0);

另一种是拷贝构建:

把实际参数直接建构造应该在的位置上,函数返回时局部对象的destructor 会执行

也就是说把x0建构在foo 的函数执行的地方

 

返回值的初始化:

X bar(){

    X xx;

    return xx;

}

返回值如何从局部对象xx拷贝而来呢?

一种方法:1.加上额外参数,类型是class object的reference,这个参数用来放置被拷贝建构的返回值 (注意拷贝建构也就是说他被放在应该在的位置,也就是说不是局部变量了)

2.return 指令之前安插一个copy constructor 调用操作 ,以便将传回的object 内容当做上述参数的初值

so 上面的程序变成了:

void bar(X& _result){

  X xx;

  xx.X::X(); //编译器产生的default ctor 调用

  _result.X::X(xx);//编译器产生的copy ctor 调用

  return ;

}

现在编译器必须转换bar的调用操作 X xx=bar();转换成 X xx; bar(xx); // 注意不用copy ctor了直接操作在xx上了 如果编译器做了优化 这就是named return value 优化

而:

bar.memfunc();//bar()传回的X 调用成员函数

变成:

X _tmp;  (bar(_tmp),_tmp).memfunc();

同样道理函数指针 X(*pf)(); 变成 void (*pf)(X&);

使用者的优化:

X bar(const T& y,const T& z){

    X xx;

    通过 y z 计算xx

   return xx;

}

这种情况下要iuxx被memberwise拷贝到编译器产生的_result中,

如果定义 ctor来利用yz计算xx则:

X bar(const T& y,const T& z){

     return X(y,z);

}

变成:

bar(X& result){

     result . X::X(y,z);

     return;

}

无需copy ctor了

 

编译器的优化:

如上所述bar中返回了具名数值 named value,因此编译器有可能自己优化,方法是以result取代named return value

bar(X& result){_result.X::X(); 直接处理result 并不处理变量xx然后复制给result 这样就优化了}

这个优化的激活需要class提供一个copy ctor~~~~~~

 

Copy ctor 要不要:

如果一个class 符合bitwise的要求那么此时member wise 的拷贝已经高效简洁 无需加入了

但是如果class需要大量的member wise 初始化操作,如用传值方式返回objects,如果是这样提供一个copy ctor 可以激活nRV named return value 优化,这就很合理了

 

成员们的初始化过程:

什么时候必须用初始化列表:

1.初始化一个reference时 2.初始化一个const member 3.调用父类ctor而且有参数时4调用member class ctor 有参数

其他情况呢:

class word{string name;int cnt;public: name=0;cnt=0;}

编译器可能这么做:

word::word{

    name.String::string();调用string的default ctor

    string tmp=string(0);

_name.string::operator=(tmp);

tmp.string::~string();

cnt=0;

}

显然name放到初始化列表会更有效率 ,会变成

name.String::String(0);

而cnt这种内建类型则没有关系,放不放到初始化列表没有效率上的差别

初始化列表究竟让编译器做了什么????

编译器会一个个操作list中的式子,以适当次序在ctor内安插初始化操作在任何explicit user code 之前。

注意的地方:

       list中的次序是按照members声明次序决定而不是list 中的排列顺序决定。

例如:class x{int i;int j; X(int val):j(val),i(j)}

错了 i先声明则i首先赋予val 然后用未初始化的j赋给i。。。

可以这样X::X(int val):j(val){i=j;}

由于会安插在explicit code 之前 所以没问题 会变成 j=val;   i=j;

可否用member functions 初始化成员??

答案是可以的,因为和objects相关的this指针已经构建妥当,只是要注意函数调用的member是否已经构建妥当了即可

------         -  -  - ----------       --       --疲惫的结束线-          - -  -     - --            -           -----

name return value TEST:

~~~~1 cl /od 不开优化

#include <iostream>
using namespace std;
class RVO
{
public:

    RVO(){printf("I am in constructor\n");}
    RVO (const RVO& c_RVO) {printf ("I am in copy constructor\n");}
    ~RVO(){printf ("I am in destructor\n");}
    int mem_var;      
};
RVO MyMethod (int i)
{
    RVO rvo1;
    rvo1.mem_var = i;
    return (rvo1);
}
int main()
{
    RVO rvo;rvo=MyMethod(5);
}

输出:

I am in constructor         //rvo 创建
I am in constructor         // rvo1创建
I am in copy constructor // rvo1赋值给hiddern
I am in destructor          // rvo1解构
I am in destructor          // hiddern解构
I am in destructor         //  rvo 解构

A MyMethod (A &_hiddenArg, B &var)
{
   A retVal;
   retVal.A::A(); // constructor for retVal
   retVal.member = var.value + bar(var);
   _hiddenArg.A::A(retVal);  // the copy constructor for A
   return;
retVal.A::~A();  // destructor for retVal

}
A MyMethod(A &_hiddenArg, B &var)
{
   _hiddenArg.A::A();
   _hiddenArg.member = var.value + bar(var);
   Return
}
~~~~2 cl /o2 代码同上
output

I am in constructor  //rvo创建
I am in constructor  //hiddern 创建
I am in destructor   //hiddern 解构
I am in destructor   //rvo解构

我不明白的是hiddern 怎么传给rvo ,我猜可能是编译器按照bitwise的复制方式进行的,此时编译器并没有直接建构结果于rvo上 ,看看下面的试验:

注:明白了, 结果直接建构在hiddern,然后通过operator = 传给rvo 。没有copy ctor因为拷贝构造函数是负责初始化的,而operator = 才是用来赋值的.

经过代码证明是对的,如果重载赋值运算符 输出变成:

I am in constructor  //rvo创建
I am in constructor  //hiddern 创建

I am in operator =   //赋值操作~~
I am in destructor   //hiddern 解构
I am in destructor   //rvo解构

~~~~3 cl /od

#include <iostream>
using namespace std;
class RVO
{
public:

    RVO(){printf("I am in constructor\n");}
    RVO (const RVO& c_RVO) {printf ("I am in copy constructor\n");}
    ~RVO(){printf ("I am in destructor\n");}
    int mem_var;      
};
RVO MyMethod (int i)
{
    RVO rvo1;
    rvo1.mem_var = i;
    return (rvo1);
}
void abc(RVO& i){
}
int main()
{
    RVO rvo=MyMethod(5);  //此时定义和赋值放到了一个表达式子
    return 0;
}

output:

I am in constructor           // rvo1 创建 注意 跟上面的第一种情况下的hiddern一样 rvo并没有调用ctor
I am in copy constructor   // rvo1 拷贝构造给rvo 此时没有hiddern了 直接构建rvo了
I am in destructor            // rvo1 析构
I am in destructor            // rvo1 解构

~~~~3 cl /o2  再来~~~~ NRV出马

I am in constructor           // rvo构建了 
I am in destructor            // rvo析构了

此时 mymethod中的一切都直接反映在rvo身上

ok~~~~~4个代码完全一样构造析构拷贝函数个数由2-6不等~~~over~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Feedback

# re: 深入探索C++对象模型读书笔记 (二)  回复  更多评论   

2010-03-11 09:13 by zmm
不错,很基础的

# re: 深入探索C++对象模型读书笔记 (二)  回复  更多评论   

2010-03-11 13:56 by zmm
~~~~3 cl /o2 再来~~~~ NRV出马

I am in constructor // rvo构建了
I am in copy constructor // rvo析构了

这里打错了吧,是in destructor吧~

# re: 深入探索C++对象模型读书笔记 (二)  回复  更多评论   

2010-03-11 14:01 by rikisand
恩 ~~ 改过来啦 你看的好仔细啊~~

# re: 深入探索C++对象模型读书笔记 (二)  回复  更多评论   

2010-03-11 16:32 by zmm
@rikisand
呵呵,因为没有看到destroy,所以我运行了一下~

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