?
在三种情况下,会以一个object的内容作为另一个class object的初值:
- object明确初始化
123
class {...};X x;X xx = x;
- object被当作参数交与某个函数
12345
extern void foo(X x);void bar(){ X xx; foo(xx);}
- 函数返回值是一个class object
12345
X foo_bar(){ X xx; ... return xx;}
如果开发者已经明确定义了一个copy constructor如下:
123 |
//copy constructor可以是多参数,其中有一个参数是其class typeX::X(const X& x);Y::Y(const Y& y); |
那么在大部分情况下,当class object以另一个同类实体作为初值时,上述constructor会被调用,这可能会导致一个暂时性class object的产生或程序代码发生改变(或二者都有)。
Default Memberwise Initialization
?
如果函数并没有提供一个explicit copy constructor,那么其拷贝同类型对象的操作由default memberwise initialization完成,其执行策略为:对每一个内建或派生的data member的值,从某一个object拷贝到另一个object。不过它不会拷贝其中的member class object,而是实施递归式的memberwise initialization(对每一个对象依次执行default memberwise initialization)。
实例
考虑如下class声明:
1234567 |
class String{public: ...//不存在explicit copy constructorprivate: char *str; int len;}; |
String object的default memberwise initialization发生于这种情况:
12 |
String noun("book");String verb = noun; |
其完成方式类似于依次各别设定每一个member:
12 |
verb.str = noun.str;verb.len = noun.len; |
如果String是另一个class的member:
1234567 |
class Word{public: ...//不存在explicit copy constructorprivate: int _occurs; String _word;}; |
那么一个Word object的default memberwise initialization会拷贝其内建的member _occurs,然后对String member执行递归式memberwise initialization。
如果开发者没有为class指定copy constructor,那么编译器也会生成implicit的声明与定义。类似于Default constructor,C++亦把copy constructor区分为trivial与non-trivial两种,只有non-trivial的实体才会被合成于程序之中。决定一个copy constructor是否为trivial的依据是class是否表现出所谓的“bitwise copy semantics”。
Bitwise Copy Semantics
1234567 |
Word noun("book"); void foo(){ Word verb = noun;} |
很明显verb是根据noun来初始化,但在未了解Word class声明之前,我们无法预测该初始化操作的程序行为。如果开发者定义了copy constructor,那么编译器会调用它。如果没有,编译器会根据class 是否展现出bitwise copy semantics,来合成一个copy constructor。
举例而言,如果Word内部数据仅仅含有内置类型,那么编译器不会合成copy constructor,而是执行bitwise copy。但如果Word内含一个String object,而String class存在一个explicit copy constructor,那么编译器将不得不合成一个copy constructor,以调用member object的copy constructor,在该合成的copy constructor中,内置类型仍然使用bitwise copy。
Bitwise Copy不出现的情况
?
一个class有四种情况不会表现出bitwise copy:
- class内含一个member object,而后者有一个copy constructor(无论是开发者指定还是编译器合成)
- class继承自一个base class,而后者存在一个copy constructor(无论是开发者指定还是编译器合成)
- class声明了一个或多个virtual functions
- class存在的继承体系内存在一个或多个virtual base class
在前两种情况下,编译器会将member或base class的copy constructors插入至合成的copy constructors中。
重设定vptr
?
在上一节我们曾经阐述过,含有virtual functions的class会在编译期间构造函数会自发扩张:
- class会增加一个vtbl
- class object会增加一个vptr
显然,vptr是决定多态机制能否正确运行的关键,当编译器将vptr导入至class之中时,该class不再具备bitwise semantics。编译器需要合成一个copy constructor,将vptr合理初始化。
实例
现有继承体系及class声明如下:
1234567891011121314151617 |
class ZooAnimal{public: ZooAnimal(); virtual ~ZooAnimal(); virtual void animate(); virtual void draw();private: ...//some data};class Bear : public ZooAnimal{public: Bear(); void animate(); void draw();private: ...//some data}; |
ZooAnimal object相互赋值或者Bear object相互赋值都可以通过bitwise copy semantics完成。在这种情况下,vptr保持bitwise copy是安全的。
当一个base class object以其derived class object内容作初始化操作时,其vptr也需要保证安全:
12 |
Bear B;ZooAnimal Z = B;//sliced |
显然,Z的vptr不应该指向Bear的vtbl,也就是说,Base class被合成出来的copy constructor会明确设定object的vptr指向Base Class的vtbl,而非从rhs处执行bitwise copy。
处理Virtual Base Class Subobject
?
一个class object如果以另一个object作为初值,而后者带有一个virtual base class subobject,那么bitwise semantics同样会失效。
在上一节中我们已经编译器需要保证在运行期明确virtual base class subobject的位置。Bitwise copy可能会破坏这个位置。
实例
现有继承体系及class声明如下:
···C++
class Raccoon:public virtual ZooAnimal{
public:
Raccoon();
Raccoon(int val);
…
private:
…//some data
}
123456789101112131415161718 |
在开发者撰写的constructor中,编译器会生成: 1. 调用ZooAnimal的default constructor代码 2. Raccoon的vptr初始化代码 3. 定位Raccoon中ZooAnimal subobject代码 编译器会将上述代码插入至两个constructor之内,并且放在开头位置。 当一个class object以其derived class object作为初值时,bitwise失效,因为此时编译器必须判断“后续当程序员试图存取其Base class subobject时程序能否正常运行。考虑如下继承体系与声明:![image_1cchtdv2o1tvlga1o4414a752613.png-20.9kB][3]```C++class RedPanda:public Raccoon{public: RedPanda(); RedPanda(int val);private: ...} |
现有object分析图如下:
在下述代码中,编译器无法判断bitwise copy是否有效,因为编译器无法了解Raccoon是否指向了一个真正的Raccoon对象:
12 |
Raccoon *ptr;Raccoon little_critter = *ptr;//可能ptr指向了派生类 |
总结
?
我们是否可以认为,在bitwise copy完全合理的情况下,应当禁止调用copy constructor以优化程序?这个问题将会在后续章节中讨论。
本节我们讨论了class不再保持bitewise copy semantics的四种情况,在这四种情况下,如果未声明copy constructor,那么编译器为了保证初始化正确,将会合成一个copy constructor。
原文:大专栏 构造函数语义学——Copy Constructor的建构操作
原文地址:https://www.cnblogs.com/wangziqiang123/p/11618218.html