目录
1、对象赋值问题引入
2、Bitwise Copy Semantics(位逐次拷贝)
3、处理class virtual function
4、处理virtual base class subobject
1、对象赋值问题引入
在C++中,有三种情况会以一个object的内容作为另一个class object的初值。这三种情况分别如下:
第一种,显式地以一个object的内容作为另一个class object的初值 X xx = x;
第二种,当object被当作参数交给某个函数时,例如:
extern void foo(X x);
void bar()
{
X xx;
//以xx作为foo()的第一个参数初值(隐式的初始化操作)
foo(xx);
//...
}
第三种,当一个函数传回一个class object时,例如
X
foo_bar()
{
X xx;
//...
return xx;
}
那么问题来了,以一个object的内容作为另一个class object的初值,C++语言是怎么进行操作的?
假设class设计者显示定义了一个copy constructor,像这个样子。
//user-defined copy constructor 的实例
//可以是多参数的形式,其第二参数以及后继参数以一个默认值供应之
X::X(const X &x);
Y::Y(const Y & y,int = 0);
在大部分的情况下,以上的问题将会调用copy constructor。少数情况会执行Bitwise Copy。
如果class没有提供一个explicit copy constructor,当class object以相同的class的另外一个object作为初值,其内部以default memberwise initialization的方法完成。也就是每一个内建的或者派生的data member的值,从一个object拷贝到另外的一个object上。不过它不会拷贝其中的member class object,而是以递归的方式施加memberwise initialization。例如,有一个下面的class声明:
class String
{
public:
//...没有explicit copy constructor
private:
char *str;
int len;
};
对于String verb = noun;语句,他的完成方式像是设定每一个members一样:
//语意相等
verb.str = noun.str;
verb.len = noun.len;
以上是一种memberwise initialization。如果String object是另外一个class的member,像这样子:
class Word
{
public:
//...没有explicit copy constructor
private:
int _occurs;
String _world;
};
对于Word verb = noun;语句,他的完成方式像是设定每一个members一样:
//语意相等
verb.occurs = noun.occurs;//bitwise copy
verb._world = String::String(noun._world);//编译器生成copy constructor,并执行拷贝构造函数
Default constructors和copy constructors在必要的时候才由编译器产生出来。这里“必要的时候”指的是当class不展示bitwise copy semantics时候。
2、Bitwise Copy Semantics(位逐次拷贝)
一个class什么时候不展示“bitwise copy semantics”?有下面的4种情况:
1)当一个class内含一个member object而后者的class声明一个copy constructor时(不论是class设计者显示的声明,还是编译器隐式的合成),例如下面的代码,String显示的声明了,而Word隐式的合成。
class String
{
public:
String(const char *);
String(const String &);
~String();
//...
};
class Word
{
public:
Word(const String &);
~Word();
//...
private:
int cnt;
String str;
};
2)当class继承一个base class而后者存在一个copy constructor时(不论是显示声明还是隐式的被合成)。例如class Dog没有拷贝构造函数,当发生一个Dog object的内容作为另一个Dog object的初值,Dog会被编译器合成一个copy constructor。
class Animal
{
public:
Animal(const char *);
Animal(const Animal &);
~Animal();
//...
};
class Dog:public Animal
{
public:
Dog(char * name);
~Dog();
};
3)当class声明一个或者多个virtual function时。
class Student
{
public:
Student();
~Student();
String virtual get_name();
};
4)当class派生自一个继承串链,其中一个或多个virtual base classes时。
class Postgraduate : virtual public Student
{
public:
Postgraduate();
~Postgraduate();
String get_Thesis_topic();
};
3、处理class virtual function
在前一节的bitwise copy semantics,提到当class声明一个或者多个virtual function时会被编译器合成一个copy constructor。此外class会进行两种扩张操作:
- 增加一个virtual function table(vtbl),内含每一个有作用的virtual function的地址。
- 一个指向virtual function table的指针(vtpr),安插在每一个class object内部。
首先,我定义两个class,ZooAnimal和Bear:
class ZooAnimal
{
public:
ZooAnimal();
virtual ~ZooAnimal();
virtual void animate();
virtual void draw();
//...
private:
//ZooAnimal 的animate() 和 draw()
//所需的数据
};
class Bear : public ZooAnimal
{
public:
Bear();
void animate();//译注:虽未写明是virtual,但它其实是virtual
void draw();//译注:虽未写明是virtual,但它其实是virtual
virtual void dance();
//...
private:
//Bear 的animate() 、 draw() 和 dance()
//所需的数据
};
然后,Bear class object以另一个Bear class object作为初值,我可以看到yogi的vptr值拷贝给了winnie的vptr了,这样的操作是安全的,它是通过编译器合成的拷贝构造函数完成的。
最后,当一个base class object以其derived class的object内容做初始化操作时,其vptr复制操作也必须保证安全,例如:
ZooAnimal franny = yogi; //译注:派生类给基类赋值
4、处理virtual base class subobject
Virtual base class 的存在需要特别的处理。一个class object如果以另一个object作为初值,而后者有一个virtual base class subobject,那么也会使“bitwise copy semantics”失效。看看如下继承关系的代码。
class ZooAnimal
{
public:
ZooAnimal();
virtual ~ZooAnimal();
virtual void animate();
virtual void draw();
//...
private:
//ZooAnimal 的animate() 和 draw()
//所需的数据
};
class Raccon : public virtual ZooAnimal
{
public:
Raccon(){/*设定private data的初值*/}
Raccon(int val){/*设定private data的初值*/}
//...
private:
//所有必要的数据
};
class RedPanda : public virtual Raccon
{
public:
RedPanda(){/*设定private data的初值*/}
RedPanda(int val){/*设定private data的初值*/}
//...
private:
//所有必要的数据
};
对于如下的代码,little_red和little_critter的关系如下图所示。
RedPanda little_red;
Raccon little_critter = little_red;
在这种情况下,为了完成正确的设定little_critter初值设定,编译器必须合成一个copy constructor,安插一些代码以设定virtual base class pointer/offset的初值。