在C#与Java的编程技术书中,关于赋值运算符经常会提到深复制与浅复制两个概念,因为C#与Java把对象分为两类:值类型和引用类型,而在C++中则没有明确这两个概念,其实在C++中的指针和引用都是引用类型的变量,C++标准库std::tr1::shared_ptr非常类似C#与Java中的引用变量,只有内置数据类型的变量才是值变量。既然在C++中也有值变量和引用变量,则在C++中也存在深复制与浅复制的问题。
图?: 深复制与浅复制
如图所示,当自定义的数据类型中存在由程序员维护的资源时,如在堆上动态分配的内存,在复制对象时需要考虑对象的深度复制,即在新对象中重新分配一块堆内存,并将源对象中的数制复制过来。如果没有复制资源,而是只复制了指向该资源的指针,便产生如图所示的情景,当对象A被销毁时,资源被释放,而对象B的指针则指向已经释放的资源,当对象B再次使用已经释放的资源时,后果将不可预料,所以在实现赋值运算符时必须考虑深复制与浅复制的问题。
一个完整、健壮、高效的赋值运算符,通常至少要考虑以下几个问题:
1) 将值赋给变量自己[4]。这种情况下不需要重新配置内存,以提高这种情况下的效率。
2) 完整复制对象[4],避免遗漏数据成员。
3) 返回变量自己的引用。主要用于连续赋值等情况,以简化代码。
4) 如果赋值失败,保持原对象不变[2]。
例如简单字符串类String的赋值运算符:
代码
class String
{
public:
String& operator=(const String& other);
private:
char* _buff;
size_t _size;
};
String& String ::operator=(const String& other)
{
if( this == &other) // 检查自赋值
return *this;
size_t size = strlen(other._buff) + 1;
if( _size
char* newBuff = new char[size]; // 可能抛出std::bad_alloc
char* bakBuff = _buff; // 备份旧内存块地址
_buff = newBuff;
delete bakBuff;
}
strcpy(_buff, other._buff);
_size = size;
return *this;
}
而在C++标准库中为了减少内存的占用,string类使用了copy-on-write技术,当为字符串赋值时并不直接分配内存,而是两个对象的指针指向同一块内存,即进行浅复制,只有当两个对象的任何一方的值发生改变,即要往内存中写入时才进行深复制,为要写入的对象分配空间。如下面的代码:
class OpAssignClass
{
public:
string str;
};
int main( void )
{
OpAssignClass oA;
oA.str = "first string";
OpAssignClass oB(oA);
OpAssignClass oC;
oC = oA;
cout
oA.str.push_back('1');
cout
oC.str.push_back('3');
cout
return 0;
}
运行结果:
oA.str.c_str( ) = 0xce02ec // 尚未写入,三个字符串指向同一块内存
oRef.str.c_str() = 0xce02ec
oC.str.c_str() = 0xce02ec
oA.str.c_str( ) = 0xcf0344 // oA写入,重新分配内存
oRef.str.c_str() = 0xce02ec
oC.str.c_str() = 0xce02ec
oA.str.c_str( ) = 0xcf0344 // oC写入,重新分配内存
oRef.str.c_str() = 0xce02ec
oC.str.c_str() = 0xcf0374
如果不对赋值运算符重载,且内部有指向动态分配的堆内存时,编译器生成的默认赋值运算符将是浅复制,造成多个对象内部指针指向同一块内存,好比C#与Java中的引用变量指向同一个对象,其运行结果将是难以控制的。而在C++中就比较灵活了,我们除了使用赋值运算符进行赋值外,我们还可以使用memcpy和memmove进行对象复制,显然,通过memcpy和memmove可以轻松实现任意类型对象的浅复制。这样我们就可以为任意类型提供深复制和浅复制:
1) 通过memcpy或memmove进行浅复制;
2) 通过赋值运算符进行深复制。
分享到:
相关推荐
用C++写的代码,基于VS2008平台写的,可以转换到VC6下,主要拿Prototype.h和Prototype.cpp以及main.cpp修改即可,目的是写来测试浅复制和深复制的区别的。
主要介绍了C++对象的浅复制和深复制详解及简单实例的相关资料,需要的朋友可以参考下
详解C++中String类模拟实现以及深拷贝浅拷贝 在C语言中/C++中,字符串是一个应用很广泛的类型,也是很基础的类型,C语言并没有直接处理字符串的操作而是采用字符指针和字符串数组进行操作,而在C++中标准库为我们...
但大多数时候,特别是类中有指针成员的时候要实现深复制,避免浅复制时,需要自己定义复制构造函数。 那么我们定义的复制构造函数什么时候调用呢,总结起来,有如下五种情况,下面分别举例说明。 一、根据一...
C/C++ 浅拷贝和深拷贝的实例详解 深拷贝是指拷贝对象的具体内容,而内存地址是自主分配的,拷贝结束之后,两个对象虽然存的值是相同的,但是内存地址不一样,两个对象也互不影响,互不干涉。 浅拷贝就是对内存地址...
关于复制构造函数的简单介绍,可以看我以前写过的一篇文章C++复制控制之复制构造函数该文章中介绍了复制构造函数的定义、调用时机、也对编译器合成的复制构造函数行为做了简单说明。本文因需要会涉及到上文的一些...
12.13章 深复制、浅复制 13.13章 管理指针成员 14.14章 重载操作符的定义 15.14章 重载输入输出操作符 16.14章 重载算术操作符 17.14章 重载关系操作符(一) 18.14章 重载关系操作符(二) 19.14章 重载...
深拷贝和浅拷贝可以简单理解为:如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝,反之,没有重新分配资源,就是浅拷贝
本文主要给大家介绍了关于c++中深浅拷贝及写时拷贝实现的相关内容,分享出来供大家参考学习,下面话不多说,来一起看看详细的介绍: 一:浅拷贝&深拷贝 浅拷贝:在拷贝构造的时候,直接将原内容的地址交给要拷贝的类...
Python中变量与C/C++/Java中不同,它是指对象的引用,Python是动态类型,程序运行时候,会根据对象的类型 来确认变量到底是什么类型。 单独赋值: 比如说: 复制代码 代码如下: >>> a = 3 在运行a=3后,变量a变成了...
深拷贝和浅拷贝可以简单理解为:如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝,反之,没有重新分配资源,就是浅拷贝
深拷贝和浅拷贝可以简单理解为:如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程是深拷贝,反之,没有重新分配资源,是浅拷贝。 位拷贝,及"bitwise assignment"是指将一个对象...
“实现对象复制”讨论有关对象复制的问题,比如如何决定是执行深拷贝还是浅拷贝,在您自己的子类中如何实现对象的复制。“Cocoa 中 Core Foundation 对象的内存管理”介绍了Cocoa代码中Core Foundation对象的内存...
原型模式可用于优化代码,深拷贝复制引用类型(string除外),浅拷贝复制指类型
试编译运行分析当前代码,体会动态内存及浅复制。 (3)利用深复制机制补充当前程序。
细节29 C#中的深复制和浅复制 细节30 全角字符转换为半角 细节31 分析路径字符串函数总结 细节32 重载一元、二元运算符 细节33 对象也能排序 细节34 实现IDisposable接口清除对象 细节35 将字符串转为字符数组 细节...
Python中关于对象复制有三种类型的使用方式,赋值、浅拷贝与深拷贝。他们既有区别又有联系,刚好最近碰到这一类的问题,研究下。 一、赋值 在python中,对象的赋值就是简单的对象引用,这点和C++不同。如下: a = ...
如第一层的算法文本为深绿色,第二层为紫色,第三层为深红色,第四层为深蓝色,第五层为浅蓝色,第六层为玫瑰红色等。 当演示递归算法执行过程中递归工作栈的变化状态时,递归工作栈显示在右侧下窗口,递归工作栈...