深度探索c++对象模型读书笔记之构造函数语意学(一)

copy constructor的构造操作

1、有三种情况可以用到复制构造函数:

1>对一个object做显示的初始化操作。例如:X x;  X xx=x;

2>当object被当做参数交给某个函数时。例如:X x; foo(x);

3>当函数传回一个class object时。例如:foo_bar(){  X xx;    return xx;}

2、合成复制构造函数:

  c++standard上说,如果class没有声明一个copy constructor,就会有隐式的声明或隐式的定义出现。和以前一样,c++Standard把copy construct区分为trivial和nontrivial两种。只有nontrivial的实例才会被合成于程序之中。决定一个copy constructor是否为trivial的标准在于class是否展现出所谓的“位逐次拷贝(bitwise copy semantics)”。

3、位逐次拷贝:

基本数据类型,如果没有在拷贝构造函数中明确指定,那么就是按数值拷贝,这就是位逐次拷贝。而class类型会调用默认拷贝构造函数进行拷贝(如果有的话),如果没有就是一个处理(成员变量一个个拷贝)。位逐次拷贝不会智能的判断被拷贝的字段是否存在引用语义(比如说的指针情况,只是按位复制了该指针指向的地址。导致他们指向同一个内存)。

测试代码:

#include<iostream>
using namespace std;
class A
{
public:
int i;
char ch;
double d;
};
int main()
{
A a1;
a1.i=1;
a1.ch='a';
a1.d=1.0;

//这会调用自动生成的拷贝构造函数吗?
A a2=a1;                   //或 A a2(a1),效果一样

return 0;
}

我们看一下VS2010下各语句对应的汇编代码:
int main()
{
//......
A a1;
a1.i=1;
00E913A8 mov         dword ptr [ebp-18h],1
a1.ch='a';
00E913AF mov byte ptr [ebp-14h],61h
a1.d=1.0;
00E913B3 fld1
00E913B5 fstp        qword ptr [ebp-10h]
A a2=a1;
00E913B8 mov eax,dword ptr [ebp-18h]
00E913BB mov         dword ptr [ebp-30h],eax
00E913BE mov         ecx,dword ptr [ebp-14h]
00E913C1 mov         dword ptr [ebp-2Ch],ecx
00E913C4 mov         edx,dword ptr [ebp-10h]
00E913C7 mov         dword ptr [ebp-28h],edx
00E913CA mov         eax,dword ptr [ebp-0Ch]
00E913CD mov         dword ptr [ebp-24h],eax
return 0;
00E913D0 xor         eax,eax
}

我们看到汇编代码中并没有调用拷贝构造函数,而是逐个字拷贝的,这种情况下并不需要编译器去合成一个拷贝构造函数。

4、什么时候一个class不展现出“bitwise copy semantics”呢?

1>当class内含一个member object而后者的class声明有一个copy constructor时(不论是被class设计者显示地声明,或者是被编译器合成)

2>当class继承自一个base class而后者存在一个copy constructor时(不论是被class设计者显示地声明,或者是被编译器合成)

3>当class声明了一个或多个virtual functions 时(注意当一个class以另一个同class的实例作为初值,都可以直接靠“bitwise copy semantics”完成)。

如果编译器对于每一个新产生的class object的vptr不能成功而正确地设置好其初值,将导致可怕的后果。因此,当编译器导入一个vptr到class之中时,该class就不再展现bitwise semantics了。现在,编译器需要合成出一个copy constructor以求将vptr适当地初始化。

class A
{
  public:
   A();
  virtual ~A();
  virtual viod  hello();
};
class B:public A
{
   public:
    viod hello();
};
void draw(const A& a){a.hello();}
int main()
{
  B tb;
  A ta=tb;
   draw(tb);//调用B::hello
   draw(ta);//调用A::hello
}

通过ta 调用virtual function hello(),调用的是A实例而非B 实例(甚至虽然ta是以B的实例tb作为初值),因为ta是一个A object。事实上,tb中B部分已经在ta初始化时被切割掉了。如果ta被声明为一个引用或指针,那么经由ta所调用的hello()才会是B的函数实例。

4>当class派生自一个继承串链,其中有一个或多个virtual base classes时(注意:如果以一个class作为另一个同类实例的初值,那么“bitwise copy”就行了)。

 

 

 

posted on 2012-09-18 19:58  探索超越  阅读(1092)  评论(0)    收藏  举报

导航