c++中的深浅拷贝以及string的传统写法和现代写法

浅拷贝

什么是浅拷贝与其危害

浅拷贝也称位拷贝,指的是编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以 当继续对资源进项操作时,就会发生发生了访问违规。
举个例子:
假设被拷贝过来的值有指针,而这个指针指向的是一块正在使用的空间,那么当拷贝结束后,就有两个对象同时管理这块空间了,这显然是不合理的, 当一个对象释放资源的时候,那么另一个对象如果继续操作这块空间,肯定会出问题的。
如图所示:
在这里插入图片描述
注意:编译器默认的拷贝构造函数是浅拷贝,通常我们需要手动实现深拷贝。

如何解决浅拷贝问题

解决浅拷贝问题可以通过深拷贝,或者使用 引用计数的写时拷贝 来完成。

引用计数的写时拷贝

知道硬链接的朋友可能容易理解点。
引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成1,每增加一个对象使用该资源,就给计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源。

深拷贝

什么是深拷贝

深拷贝的出现就是为了解决浅拷贝不能完成的工作,如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。

深拷贝的实现

深拷贝会给每个对象独立分配资源,保证资源使用不冲突。深拷贝有两种不同的实现方式,一种是传统写法,一种是现代写法。 两者差异体现在过程的繁琐与否上,下面通过string的拷贝构造和赋值运算符重载来演示 传统写法与现代写法的差异。

传统写法

特点:可读性强但代码相对繁琐。(自己当工人)
string的传统写法

#include <string.h>
namespace mystring {
	class string
	{
	public:
		string(const char* s = "") {
			if (nullptr == s) {
				assert(false);
				return;
			}
			_str = new char[strlen(s) + 1];
			strcpy(_str, s);
		}
		string(const string& s) 
			:_str(new char[strlen(s._str)+1])
		{
			strcpy(_str, s._str);
		}
		string& operator=(const string& s) {
			if (this != &s) {
				char* tmp = new char[strlen(s._str) + 1];
				strcpy(tmp, s._str);
				delete[] _str;
				_str = tmp;
			}
			return *this;
		}
		~string() {
			delete[] _str;
			_str = nullptr;
		}

	private:
		char* _str;
	};

现代写法

特点:相对抽象但代码简洁巧妙。(找工人干活swap)
string的现代写法

namespace mystring2 {
	class string {
	public:
		string(const char* s = "") {
			if (s == nullptr) {
				s = "";
			}
			_str = new char[strlen(s)];
			strcpy(_str, s);
		}
		string(const string& s)
			:_str(nullptr)
		{
			string tmp(s._str);//调用了string(const char* s = "") 复用代码
			swap(_str, tmp);
		}
		string& operator=(const string& s) {
			if (this != &s) {
				swap(_str, s._str);
			}
			return *this;
		}
		~string() {
			if (_str != nullptr) {
				delete[] _str;
				_str = nullptr;
			}
		}
	private:
		char* _str;
	};
	void test1() {
		s1 = "hello";
		s2 = "";
		s2 = s1; //调用拷贝构造,再调用赋值运算符的重载 传参进去的时候拷贝一次 紧接着交换临时变量与原来对象指向的空间 
	}
}

猜你喜欢

转载自blog.csdn.net/ifwecande/article/details/106396229
今日推荐