【C++】深拷贝以及string类的其传统版与现代版写法

本文深入探讨了C++中的浅拷贝与深拷贝概念,通过具体实例对比了两种拷贝方式的差异及潜在问题。介绍了如何在类中实现深拷贝,避免资源管理错误,并提供了传统版与现代版string类的实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

浅拷贝:

浅拷贝,也称为拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,还是按照正常的状态来处理,所以当继续对资源项操作时,就会发生访问违规。要解决浅拷贝问题,C++中引入了深拷贝。

深拷贝:

如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。

1、传统版写法的string类

class String
{
public:
	String(const char* str = "")
	{
		//构造string类对象时,如果传递nullptr指针,认为程序非法,此处断言
		if (str == nullptr) {
			assert(false);
			return;
		}
		_str = new char[strlen((str)+1)];
		strcpy(_str, str);
	}

	String(const String& s)
		:_str(new char[strlen(s._str) + 1])
	{
		strcpy(_str, s._str);
	}

	String& operator=(const String& s)
	{
		if (&s != this) {
			char* pStr = new char[strlen(s._str) + 1];
			strcpy(pStr, s._str);
			delete[] _str;
			_str = pStr;
		}
		return *this;
	}

	~String()
	{
		if (_str) {
			delete[] _str;
			_str = nullptr;
		}
	}
private:
	char* _str;
};

 2、现代版写法的string类

观察上面给出的传统版写法string类,我们可以发现,在构造、拷贝构造以及赋值中,代码的重复度很高,人们为了解决这个问题,又给出了string类的现代版写法。

class String
{
public:
	String(const char* str = "")
	{
		if (str == nullptr) {
			str = "";
		}
		_str = new char[strlen(str) + 1];
		strcpy(_str, str);
	}
	String(const String& s)
		:_str(nullptr)
	{
		String strTmp(s._str);//根据s创建一个临时对象,此对象具有资源
		swap(_str, strTmp);//将这个对象与_str交换,_str拿到这个资源,而这个临时对象得到nullptr
	}

	String& operator=(String s)
	{
		//在传统的赋值中,参数传入的是一个对象的引用,此处使用了直接传值的办法
		//在传引用的时候,我们也需要进行this!=&s的判断,此时传值,一定不需要这步判断
		//且传引用时,如果采用swap的方法,我们还需临时创建一个对象,来完成。
		//但如果直接传值,我们知道在实现的时候,它会自动利用传过来的值创建一个临时对象
		//我们就可以直接使用s._str了,非常巧妙
		swap(_str, s._str);
		return *this;
	}

	~String()
	{
		if (_str) {
			delete[] _str;
			_str = nullptr;
		}
	}
private:
	char* _str;
};

 

### 深拷贝浅拷贝的基本区别 深拷贝和浅拷贝是C++中处理对象复制时的两种方式。**浅拷贝**指的是仅复制对象中的基本型数据,而对于指针型的成员变量,只复制其地址值,并不会重新分配内存并复制内容。这种操作可能导致多个对象共享同一块堆内存,从而在析构时引发重复释放的问题[^1]。 例如,默认的拷贝构造函数对指针进行的是浅拷贝: ```cpp class Person { public: std::string name; int* age; // 默认拷贝构造函数(浅拷贝) Person(const Person& p) { name = p.name; age = p.age; // 仅复制指针地址 } ~Person() { delete age; } }; ``` 上述代码中,`age`指针没有进行深拷贝,两个对象的`age`指向相同的内存空间,当两个对象析构时会尝试两次释放同一块内存,导致未定义行为[^3]。 --- ### 深拷贝的应用场景及实现方法 为了解决浅拷贝带来的问题,**深拷贝**会在拷贝构造函数中为指针成员重新申请一块新的内存,并将原对象的数据复制进去,使新旧对象之间不再共享堆内存。这种方式适用于中包含动态分配的资源(如指针、文件句柄等)的情况[^5]。 例如,修改拷贝构造函数以实现深拷贝: ```cpp class Person { public: std::string name; int* age; // 自定义拷贝构造函数(深拷贝) Person(const Person& p) { name = p.name; age = new int(*p.age); // 新建内存并复制内容 } ~Person() { delete age; } }; ``` 这样,每个对象都有独立的堆内存,避免了重复释放的问题。同时,在函数参数传递或返回对象时,也会触发拷贝构造函数,因此需要特别注意是否使用了深拷贝来防止资源冲突[^1]。 --- ### 外实现拷贝构造函数的方法 在实际开发中,为了保持定义的简洁性和模块化,通常会将拷贝构造函数在外部实现。此时需使用作用域解析运算符 `::` 来指定该函数属于哪个。例如: ```cpp class Person { public: std::string name; int* age; Person(); // 构造函数 Person(const Person& p); // 声明拷贝构造函数 ~Person(); }; // 在外实现拷贝构造函数 Person::Person(const Person& p) { name = p.name; age = new int(*p.age); // 深拷贝 } ``` 这样的写法不仅使得头文件更加清晰,也便于维护和调试。此外,对于模板的拷贝构造函数,同样可以在外实现,但需要注意添加模板参数列表: ```cpp template <typename T> class Array { public: T* data; int size; Array(int s); Array(const Array<T>& arr); // 模板的拷贝构造函数声明 ~Array(); }; // 外实现模板的拷贝构造函数 template <typename T> Array<T>::Array(const Array<T>& arr) { size = arr.size; data = new T[size]; for (int i = 0; i < size; ++i) { data[i] = arr.data[i]; // 深拷贝 } } ``` 此实现方式有助于提升代码可读性,并增强封装性[^2]。 --- ### 浅拷贝深拷贝在实际应用中的注意事项 当对象中存在指针成员时,除了在复制对象时需要考虑自定义拷贝构造函数,还应该关注以下两种情形: - **函数参数为对象时**:实参传递给形参的过程实际上是调用拷贝构造函数创建了一个临时对象。 - **函数返回值为对象时**:返回的对象通常是函数内部对象的一个拷贝,系统也会调用拷贝构造函数生成该副本。 如果未正确实现深拷贝,则在这些过程中可能会导致堆内存重复释放或数据不一致等问题[^4]。 此外,使用智能指针(如 `std::shared_ptr` 或 `std::unique_ptr`)可以自动管理内存生命周期,避免手动实现深拷贝和浅拷贝带来的资源管理问题,是一种更现代且安全的替代方案。 --- ### 总结 深拷贝浅拷贝的核心区别在于是否为指针成员重新分配内存并复制内容。默认的拷贝构造函数执行的是浅拷贝,可能导致堆内存重复释放的问题;而通过自定义拷贝构造函数实现深拷贝可以有效解决这一问题。在外实现拷贝构造函数有助于提高代码的可维护性和模块化程度,尤其适用于模板等复杂结构。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值