您的位置:首页 > 运维架构

浅拷贝、深拷贝 Bitwise Copy和Memberwise Copy

2015-06-23 22:48 211 查看
转载:

/article/2755556.html



C++中类的默认的拷贝构造函数是按位拷贝的,也就是直接将一个对象的成员值拷贝过来;

比如一个类A,我们不显示提供拷贝构造函数的话:

如下:

class{
int a;
char arr[10];
char *p;
};
A a1;
A a2=a1;


这个时候,a2和a1的成员 int a和arr[ ]是相同的值,成员是独立的,修改某一个的成员不会影响另外一个,但是这个时候要注意的是指针p;

虽然我们输出的结果一样,但是不能草率下决定。因为这个时候a2和a1的p值是一样的,也就是说两个指针指向同一个地方;

所以这样的拷贝会经常出现问题,比如两次释放指针的内存等。解决办法大家都知道是自己构造一个按内容拷贝的拷贝构造函数,这里我也就不多说了。

我想讨论的问题是如下的问题:

C++编译器在什么情况下不会按照默认的Bitwise copy来做呢?

首先有两种情况我不想说,1:就是含有的成员对象本身提供了拷贝构造函数(不管是默认的还是自己提供的),这个时候当拷贝这个对象的时候调用的是对象的类提供的拷贝构造函数。2:继承的基类有拷贝构造函数,这个时候编译器会插入基类的拷贝构造函数,而不是编译器自己来提供。这两种都不会按照默认的拷贝语意来做。

我想讨论下面两种:

3:含有虚函数的类;

考虑下面的代码:

class Foo
{
public:
int a;
Foo *next;
virtual void Func(void){cout<<"call Foo::func()"<<endl;}
};
class D:public Foo
{
public:
int c;
virtual void Func(void){cout<<"call D::func()"<<endl;}
};
int main(void)
{
D d;
Foo f=d;
d.Func();
f.Func();
system("pause");
return 0;
}


输出的结果是什么呢?运行看看:结果完全按照我们想要的要求运行:call D::func() call Foo::func()

所以这个就是说,当含有虚函数的时候,我们的类会有虚函数表,每个对象会有vptr。如果我们的基类只是简单的将子类的vptr值拷贝过来,显然是不符合要求的,于是这个时候编译器会给我们自动的停用bitwise拷贝,而用reset将基类的vptr指向基类的虚函数表。所以这个时候是达到了我们的要求的。我想说的是,其他值会按照默认的按位拷贝,只是vptr不会,还有我们下一个将讨论的也不会。

4:虚继承

与第三种情况类似,但是有区别,这里有vbtl,然而vbtl是采用的重新初始化,而不是reset;

让我们看看例子:

class Raccoon : public virtual ZooAnimal
{
public: Raccoon() { /* private data initialization */ }
Raccoon( int val ) { /* private data initialization */ }
// ... private: // all necessary data
};
class RedPanda : public Raccoon
{
public: RedPanda() { /* private data initialization */ }
RedPanda( int val ) { /* private data initialization */ }
// ... private: // all necessary data
};


这里如果是两个一样的类的对象间进行拷贝,简单的按位拷贝就会解决问题,而我们的问题在于父类与子类之间的拷贝;

如:RedPanda little_red;

Raccoon little_critter = little_red;

这个时候,编译器会在默认的拷贝构造函数中插入初始化指向虚基类的指针,而不是reset。这个与类的内部存储结构有关,建议看看之后便一目了然了。

memberwise copy和bitwise copy

首先说一下深拷贝(memberwise copy)和浅拷贝(bitwise copy)的问题。一般来说,自己定义的copy ctor对于对象的拷贝会有严格的、符合语义的定义(人为错误、破坏因素除外)。然而,无论是自定义的还是默认的ctor,编译器都会插入对虚拟机制的处理代码,这就保证对象切片和拷贝正确的发生——可能会出乎你的意料,但符合C++的语法语义。

虚拟机制与拷贝方式

当类中没有虚拟机制、没有其他类对象的成员时(只包含built-in类型、指针或者数组),默认copy ctor进行的是bitwise copy,这会导致对象切片的发生。然而,当类中有虚拟机制,或者有其他类对象成员时,默认copy ctor采用的是memberwise copy,并且会对虚拟机制进行正确的拷贝。

因为包含虚拟机制的类在定义一个对象时,编译器会向ctor中添加初始化vtable和vbaseclasstable(依赖于具体编译器)的代码,这样可以保证vtable中的内容与类型完全匹配。也就是说MyBase和DerivedMyBase有这相似的VTABLE,但不是完全相同——例如DerivedMyBase中还可以定义自己的virtual函数,这样它的VTABLE就会有更多表项。

而多态的实现是通过将函数调用解析为VTABLE中的偏移量来实现。pMB->Get()可能会被编译器解析成:

(*pMB->__vtable[Offset_of_Get])();

而当MyBase作为虚基类时,访问其中的数据成员可能就是:

pMB->__vBaseClassMyBase->b;

那么,当“aMB = aDMB;”,copy ctor会执行memberwise copy,正确的初始化aMB的VTABLE,而不是仅仅将aDMB的VTABLE拷贝过来。如果是bitwise copy,aMB对象中的VTABLE将是aDMB的,aMB.Get()调用的将是DervieMyBase定义的Get(),这显然是不符合语义和逻辑的。

总而言之

对象切片和copy ctor是一个很复杂的东西,在有虚拟机制的情况下两者是紧密结合在一起的。因为对象切片和拷贝构造函数的问题,不通过指针或者引用无法达到多态的目的。

还有一个问题是赋值拷贝的问题,这个机制更复杂,因此Lippman建议不要再虚基类中使用数据成员。C#和java禁止了多重继承,并将interface作为一个单独的东西,消除了赋值拷贝带来的复杂性。关于赋值拷贝的问题,有机会再讨论。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: