上一篇文章介绍了编译器为default constructor在代码背后所做的处理,本文则讲解copy constructor背后的故事。
copy constructor
有三种情况,会用到copy constructor
class X
{
...
}
X xx = x;
extern void foo(X x);
void bar()
{
X xx;
foo(xx);
}
X foo_bar()
{
X xx;
...
return xx;
}
如果类的设计者显示的定义的copy constructor:
X::X(const X& x) { ... }
在上面的三种情况下,copy constructor都会被调用。然而,如果类没有提供显示的copy constructor,编译器会做怎样的处理呢?
default member-wise initialization
如果一个类的实例以另一个实例作为初值,如X xx = x;
,且没有提供显示的copy constructor,后者会被以default member-wise的手法进行其内部所有成员的初始化:
class String
{
public:
char* str;
int len;
};
String noun("book");
String verb = noun;
default member-wise的初始化会对String
中每一个成员初始化:
verb.str = noun.str;
verb.len = noun.len;
如果String
作为成员存在某一个类中,对这个类的default member-wise initialization会是递归的。参考下面的代码:
class Word
{
public:
// 没有显示的copy constructor
private:
int m _occurs;
String m_word;
}
Word
的default member-wise initialization会复制成员m_occurs
,并且对m_word
中的成员递归实行赋值。我的理解是,default member-wise initialization是编译器实现的bit-wise的浅拷贝,String verb = noun;
即memcpy(&verb, &noun,sizeof(String));
。
ARM指出,default member-wise initialization是被copy constructor实现出来的。联想到default constructor,你或许会疑惑,如果一个类没有定义copy constructor,编译器是否一定为它合成出一个copy constructor,而不是像default constructor一样,只在编译器需要的时候才被合成出来?《对象模型》指出,答案是后者,copy constructor也只在必要的时候被合成出来。对于default constructor,有四种情况编译器不会把它合成出来;而对于copy constructor,必要的情况指的是当类不展现bit-wise copy语义的时候。
和default constructor一样,copy constructor也有trivial和non-trivial之分,只有当copy constructor是non-trivial的时候它才会被隐式地合成出来。决定它是否是trivial则取决于类是否展现bit-wise copy的语义。
bit-wise copy语义
接下来探究什么时候类会展现出bit-wise copy的语义。考虑下面的代码:
#include "Word.h"
Word noun("book");
void foo()
{
Word verb = noun;
}
Word.h里面包含了Word
的详细定义。但是在看到Word
的内容之前,我们不知道Word
的设计者是否提供了copy constructor,所以我们无法预测这个初始化的行为。如果Word
没有显式定义copy constructor,编译器是否会为它产生一个呢?这得看Word
是否展现bit-wise语义。看看下面Word
的声明:
class Word
{
public:
Word(const char*);
~Word() { delete[] str; }
private:
int cnt;
char* str;
};
《对象模型》一书中说,此时Word
展现出了default copy的语义,所以不会有default copy constructor被合成出来。其实对于这一段我很疑惑,如果没有copy constructor被合成出来,是如何施行default member-wise initialization手法,为verb
中的每个成员赋值的呢?书中提到这种情况下不会产生函数调用,是不是意味着Word verb = noun
会被展开成verb.str = noun.str; verb.len = noun.len;
?
书中也给出了Word
的另一种声明,即Word
不展现bit-wise copy语义的情况:
class Word
{
public:
Word(const String&);
~Word();
private:
int m_cnt;
String m_str;
};
class String
{
public:
String(const char*);
String(const String&);
~String();
};
Word
中的String
类显示声明了copy constructor,为此编译器需要合成出copy constructor,其中包含了调用String
的copy constructor的代码:
inline Word::Word(const Word& wd)
{
str.String::String(wd.str);
m_cnt = wd.m_cnt;
}
当然,Word
中可能存在的,注入整数、指针等成员也会被复制。
四种不展现bit-wise copy语义的情况
- 当类中包含了一个对象,且后者拥有一个copy constructor。这个copy constructor可以是显式声明的,如前面的
String
,也可以是被合成出来的,如Word
。 - 当类继承自某个基类,且后者拥有一个copy constructor。again,copy constructor可以是显示的或隐式的。
- 当类中声明了虚函数。
- 当类拥有虚基类。
前两点和default constructor类似,已有文章介绍。下面讨论情况3和4。
类中有虚函数
当类中声明了虚函数,编译器会对其成员进行扩张,即:
- 增加一个虚函数表vtbl;,包含每个虚函数的地址。
- 增加一个虚表指针vptr指向vtbl。
显然需要对对象中的vptr进行精心设置保证正确的行为,这个类也因此不再有bit-wise copy的语义了。编译器需要合成出一个copy constructor来设置vptr。
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();
virtual void dance();
private:
// some data
};
ZooAnimal alpha;
ZooAnimal beta = alpha;
Bear gamma;
Bear delta = gamma;
最后的两种赋值都可以通过bit-wise语义完成,alpha
的vptr被拷贝给beta
的vptr,gamma
的vptr被拷贝给delta
的vptr,因为vtbl是一样的,所以是安全的。而如果基类对象从其子类对象赋值而来:
ZooAnimal epsilon = gamma;
epsilon
的vptr不可以被设定为指向基类Bear
的vtbl,bit-wise语义因此失效。如果强行对epsilon
进行bit-wise copy的话,多态会受到影响。因此需要在ZooAnimal
的copy constructor中显式设定vptr指向ZooAnimal
的vtbl,而不是从右值中获得。
void Draw(const ZooAnimal& zoey) { zoey.draw(); }
void foo()
{
ZooAnimal epsilon = gamma;
Draw(epsilon);
Draw(gamma);
}
类拥有虚基类
bit-wise copy也会对虚继承造成破坏。考虑下面的例子:
class Raccoon: public virtual ZooAnimal
{
public:
Raccoon() { /* 设定private data初值*/ }
Raccoon(int val) { /* 设定private data初值*/ }
private:
// some data
};
class RedPanda: public Raccoon
{
public:
RedPanda() { /* 设定private data初值*/ }
RedPanda(int val) { /* 设定private data初值*/ }
private:
// some data
};
RedPanda panda;
Raccoon raccoon = panda;
问题出在RedPanda
中的Raccoon
成分,编译器需要在copy constructor中将raccoon
的虚基类指针和偏移量准备妥当。
0 条评论