C++ Object Model:Default Memberwise Initialization/bitwise copy semantics及深拷贝/浅拷贝

 Default Memberwise Initialization和bitwise copy semantics的区别

当class object是以“相同class的另一个object“作为初值时,其内部是以所谓的default memberwise initialization手法完成的,也就是把每一个内建的或派生的data member的值,从某个object拷贝一份到另一个object身上。不过它不会拷贝其中的member classobject,而是以递归的方式实施memberwise initialization。这种递归的memberwise initialization是如何实现的呢?答案就是Bitwise Copy Semantics和default copy constructor。如果class展现了Bitwise Copy Semantics,则使用bitwise copy,否则编译器会生成default copy constructor。 那什么情况下class不展现Bitwise Copy Semantics呢?有四种情况:

  1. 当class内含有一个member class object,而这个member class 内有一个默认的copy 构造函数不论是class设计者明确声明,或者被编译器合成
  2. 当class 继承自 一个base class,而base class 有copy构造函数[不论是class设计者明确声明,或者被编译器合成]
  3. 当一个类声明了一个或多个virtual 函数
  4. 当class派生自一个继承串链,其中一个或者多个virtual base class

下面我们来理解这四种情况为什么不能使用bitwise copy,以及编译器生成的copy constructor都干了些什么。

在前2种情况下,编译器必须将member或者base class的“ copy constructor的调用操作”安插到被合成的copy constructor中

第3种情况下,因为class 包含virtual function, 编译时需要做扩张操作:增加virtual function table;创建一个指向virtual function table的指针。所以,编译器对于每一个新产生的class object的vptr都必须被正确地赋值,否则将跑去执行其他对象的function了,其后果是很严重的。因此,编译器必须合成copy Constructor并将vptr适当地初始化

第4种情况下类似。


  一、首先要分清楚这两者之间的关系:两个概念属于不同“集合”(但有部分交集)。

1. Default Memberwise Initialization是与user defined Initialization相对应的。是从编译器(计算机)与程序员(用户)的角度出发;

2. bitwise copy 是与memberwise copy相对应的。是两种不同的拷贝方式,编译器通常为了效率会选择bitwise方式拷贝(尤其针对于POD(=Plain Old Data)类型)。

那么为什么这两个概念经常会混淆呢?主要原因是二者有部分交集——在类的对象初始化或者赋值(operator=)时,两个概念会同时出现。对象整体角度出发,默认的对象赋值操作和初始化操作default assignment and initialization ),编译器会选择memberwise方式(这里不是指memberwise copy,更确切的说应该是:individually assignment or initialization)操作,即对构成对象中的每一个成员数据分别进行赋值或者初始化。从对象的数据成员角度出发,具体到对象的每一个数据成员的操作,编译器通常采用(可以认为就是)bitwise copy操作,就像memcpy或者memset函数一样,原样将内存中的数据按位复制一份。


默认拷贝构造函数(Default Copy Constructor)、默认赋值运算符(operator =)和默认析构函数,是C++类中的六大特殊成员函数中的三个。三者同时遵循一个原则:“一荣俱荣、一损俱损”。如果三者其中的任意一个被显示定义了(defined)那么三者必须都被显式定义。当果三者之一被程序员调用但未没有被显式声明时,编译器会隐含的实现这三个特殊成员函数。当用一个类对象去初始化另一个类对象时,需要用到拷贝构造函数当用一个类对象去设定另一个类对象时,需要用到赋值运算符拷贝构造函数与赋值运算符都遵循“Default Memberwise Assignment&Initialization”原则,即对类中的每一个数据成员进行依次复制,但是通常编译器只采用bitwise copy方式复制(这样能够提高效率)。例如,对于只含有POD成员数据的简单类,bitwise copy方式绰绰有余。但是以下几种情况比较特殊,什么情况下需要实现默认的构造函数呢?

自然是编译器需要它的时候(切记不是程序员需要的时候),通常以下四种情况,需要编译器来实现默认的构造成员函数(default constructor):

1) 类中含有成员类对象,并且此类对象含有默认构造函数;

这种情况下,如果没有显示的定义构造函数,那么需要一次构造类中定义的所有成员,当构造成员类对象(member class object)的时候,需要调用此成员类的默认构造函数,所以这时候需要编译器构造出默认的构造函数,来调用成员类的默认构造函数

2) 类的基类中至少有一个含有默认的构造函数;

如果没有显式的定义构造函数,同样编译器构造派生类的时候,必然需要调用基类的构造函数,所以需要编译器在派生类中构造出默认的构造函数。

3) 类中含有虚函数(virtual function);

4) 类中含有虚基类(virtual base class);

由于虚拟机制的原因,这两种情况下,需要编译器来完成虚函数表(vbtl)的初始化和虚表指针(vptr)的初始化,所以如果没有显式的定义构造函数,需要编译器构造默认的构造函数。(本身虚拟机制就是从编译器角度来实现的)。其他比较简单的情况(类的成员数据都是POD=Plain Old Data),在MSVC中经过O2选项优化编译后,简单的类直接被转换为几个连续定义的变量,自然就不需要默认的构造函数了。


  编译器编译连接角度 ,以上四种情况下如果未定义拷贝构造函数,编译器为了编译工作的顺利进行,会自定义拷贝构造函数;(但是:此时的默认拷贝构造函数依然只能完成对象的栈对象的创建和拷贝,无法完成堆上动态内存的拷贝构造,故此时依然属于浅拷贝! 从编程者角度 ,如果类比较复杂(例如含有指针、引用、虚函数等),单单依靠编译器定义的 bitwise 版本默认拷贝构造函数,程序是无法达到预定效果的(即使已经由默认bitwise copy转为拷贝构造函数,依然是浅拷贝,那区别在哪里呢?拷贝构造函数比bitwise多了扩张的成员类构造函数、虚函数表构造等内容,这些是bitwise无法完成的,编译器为了防止含成员类的类或派生类混乱而使用扩张法调用了经过扩张的默认构造函数,主要完成了类结构意义上的创建和初始化,但不改变其依然对动态内存缺失拷贝手段的浅拷贝本质。),所以此时往往需要程序员显式定义出自己的拷贝构造函数(只有这个时候才是深拷贝)

实现分类 实现方式 备注 深拷贝/浅拷贝
Default Memberwise Initialization bitwise copy 默认实现,适合Plain Old Data,数据和指针memcpy 浅拷贝
default copy constructor 四种情况下创建,创建成员类和虚函数列表、虚继承列表的整体框架,局部成员变量与bitwise一样对数据和指针memcpy 浅拷贝
User defined Initialization User defined copy constructor 用户自定义,调用成员类和虚类构造函数以完成框架构造,为动态内存分配空间并拷贝 深拷贝

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值