深度探索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”就行了)。
浙公网安备 33010602011771号