构造函数语义学——Copy Constructor的建构操作

 
在三种情况下,会以一个object的内容作为另一个class object的初值:

  1. object明确初始化

    1
    2
    3
    class {...};
    X x;
    X xx = x;
  2. object被当作参数交与某个函数

    1
    2
    3
    4
    5
    extern void foo(X x);
    void bar(){
    X xx;
    foo(xx);
    }
  3. 函数返回值是一个class object

    1
    2
    3
    4
    5
    X foo_bar(){
    X xx;
    ...
    return xx;
    }

如果开发者已经明确定义了一个copy constructor如下:

1
2
3
//copy constructor可以是多参数,其中有一个参数是其class type
X::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声明:

1
2
3
4
5
6
7
class String{
public:
...//不存在explicit copy constructor
private:
char *str;
int len;
};

String object的default memberwise initialization发生于这种情况:

1
2
String noun("book");
String verb = noun;

其完成方式类似于依次各别设定每一个member:

1
2
verb.str = noun.str;
verb.len = noun.len;

如果String是另一个class的member:

1
2
3
4
5
6
7
class Word{
public:
...//不存在explicit copy constructor
private:
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

1
2
3
4
5
6
7


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:

  1. class内含一个member object,而后者有一个copy constructor(无论是开发者指定还是编译器合成)
  2. class继承自一个base class,而后者存在一个copy constructor(无论是开发者指定还是编译器合成)
  3. class声明了一个或多个virtual functions
  4. class存在的继承体系内存在一个或多个virtual base class

在前两种情况下,编译器会将member或base class的copy constructors插入至合成的copy constructors中。


重设定vptr

 
在上一节我们曾经阐述过,含有virtual functions的class会在编译期间构造函数会自发扩张:

  1. class会增加一个vtbl
  2. class object会增加一个vptr

显然,vptr是决定多态机制能否正确运行的关键,当编译器将vptr导入至class之中时,该class不再具备bitwise semantics。编译器需要合成一个copy constructor,将vptr合理初始化。

实例

现有继承体系及class声明如下:
image_1cchrq8qc1m3sqpgqhe15te9389.png-6.9kB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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也需要保证安全:

1
2
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声明如下:
image_1cchsh99imb2u671bk629s13ubm.png-13.9kB
···C++
class Raccoon:public virtual ZooAnimal{
public:
Raccoon();
Raccoon(int val);

private:
…//some data
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
在开发者撰写的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分析图如下:
image_1cchtl7dq17ulenn30r19ej1tft20.png-130.2kB
在下述代码中,编译器无法判断bitwise copy是否有效,因为编译器无法了解Raccoon是否指向了一个真正的Raccoon对象:

1
2
Raccoon *ptr;
Raccoon little_critter = *ptr;//可能ptr指向了派生类


总结

 
我们是否可以认为,在bitwise copy完全合理的情况下,应当禁止调用copy constructor以优化程序?这个问题将会在后续章节中讨论。
本节我们讨论了class不再保持bitewise copy semantics的四种情况,在这四种情况下,如果未声明copy constructor,那么编译器为了保证初始化正确,将会合成一个copy constructor。

原文:大专栏  构造函数语义学——Copy Constructor的建构操作


猜你喜欢

转载自www.cnblogs.com/wangziqiang123/p/11618218.html
今日推荐