C++——浅拷贝深拷贝写时拷贝详解

Posted 康x呀

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++——浅拷贝深拷贝写时拷贝详解相关的知识,希望对你有一定的参考价值。

C++——浅拷贝、深拷贝、写时拷贝详解

浅拷贝与深拷贝

用String类模拟用 将“/0”拷贝进去:

调用系统默认的拷贝构造函数,结果就是内容相同,地址相同。

说明这个方法是浅拷贝方法。
浅拷贝方法带来的问题就是同一个空间被析构两次,程序就直接崩了。如下图所示:

浅拷贝就是只有s和s1两个对象同时指向一个地址空间,而析构函数需要执行两次,因而会对内存空间进行两次析构

将代码中的拷贝构造函数写完整后,程序就不崩溃了,此方法为深拷贝方法:

深拷贝方法就是对以前的地址空间再进行拷贝一份,使得拷贝的对象指向新的地址空间,这样就可以避免一个空间同时被析构两次的问题。

那么问题就是:深拷贝就一定好吗?

深拷贝最大的特点就是拷贝一个一模一样的空间,但是缺点是比较浪费空间。构造构对象越多,单独生成的空间就越多且都一样。从用户来看,数据从哪里来并不重要,然而浅拷贝会造成数据空间被释放多次。因此浅拷贝有浅拷贝的好,深拷贝有深拷贝的好!

解决浅拷贝的问题 —— 引用计数

引用空间就是给这个空间一个数代表这个空间目前有两个对象在使用,析构的时候就减少相应的次数,只要不是0就说明这个空间还有对象在用。

使用use_count这个变量去做(因为要对所有对象使用,所以必须是静态的static);每一个对象应该维护自己的引用计数,否则引用同一个就会导致区分不了不同的对象。

具体做法:

首先增加一个字符串引用计数器类:

// 增加字符串引用类;
// An highlighted block
class String_rep

	friend class String;
	friend ostream& operator<<(ostream &out, const String &s);
public:
	String_rep(const char *str = "") :use_count(0)
	
		m_data = new char[strlen(str) + 1];
		strcpy(m_data, str);
	
	String_rep(const String_rep &rep) :use_count(0)
	
		m_data = new char[strlen(rep.m_data) + 1];
		strcpy(m_data, rep.m_data);
	
	String_rep& operator=(const String_rep &rep);
	~String_rep()
	
		delete[]m_data;
		m_data = nullptr;
	
public:
	void increment()
	
		++use_count;
	
	void decrement()
	
		if (--use_count == 0)
		
			delete this; 
		
	
private:
	char *m_data;
	size_t use_count;
;

现在的字符串类私有成员只包含一个字符串计数引用类:


字符串内部行为发生了变化,由原先的字符指针变成了一个类的指针类型
m_rep是指针调动构造方法,调动了String_rep的构造方法,有两个成员m_data和use_count 共同组成一个字符串引用计数器的对象,但是没名字,它会根据字符串内容开辟空间。最后返回的地址是m_rep。最终手动申请的动态对象最终由m_rep指向。

把对象初始化好,m_rep要加计数,因此专门给它设计了两个方法。

实例化完这个对象后,就要调动加计数器更新。
对于新的s1对象,这两个对象没有关联的地方,都有各自的引用计数器指针,互不干扰。

对于s2对象,进行浅拷贝赋值的时候,同时指向s的引用计数器空间,引用计数器会更新。所以引用计数器是一个对象,不仅负责对空间的计数,还有一个指针指向这个空间。

这样不同对象就拥有了不同的计数器。

但是析构的时候怎么析构?检查的时候会发现内存泄漏
因为new申请的空间都没释放。

对象要析构,就是减少引用计数。

但是减少引用计数之后并没有做任何操作

因此当计数器减为0的时候,给其设计一个自杀规则

自杀,防止了内存泄漏,其他对象析构的时候就会调用析构函数减少计数器数量,当最后一个对象析构的时候先调用自身析构方法,然后也紧接着要减少计数器,当等于0的时候,就delete this。

问题:这个this是谁?

这个this就是当前对象s内部的指针m_rep,m_rep是String_rep类型,先调自己的析构方法,而它的析构方法是先释放空间:

释放的是String_rep所指向的空间

但是先不能释放String_rep对象释放掉,那么留下的那个空间就会造成内存泄漏。所以先要把这个对象指针所管理的资源给释放掉。不然就没机会释放了。

**在此这里先释放m_data的空间(指针所指的资源),否则造成内存泄漏,也就是通过析构函数先回收资源,搞成空壳子之后再释放。**因此这里delete this先调用了String_rep自身的析构函数。

类里的东西属于静态的,它可以自己释放,new申请的叫动态的,需要手动释放(所谓动态内存管理)。

赋值操作怎么写?

就是说s2给s1赋值,呢就是说s1不在指向s,而是指向s2的空间,那么s的空间引用计数器就要减1,s2的空间引用计数器就要加1。

写时拷贝

对于这种情况,拷贝构造的时候,因为是浅拷贝,指向相同的空间,就是将s1的内容修改后,那么势必会影响s对象的修改。

多个对象指向了一个空间,若一个对象要修改内容,那么就不能共同使用一个空间了,称之为”写时拷贝
让这个要修改的对象单独去完成这个操作,把它深拷贝出来。就是新指向的空间引用计数器得加1,原先的空间引用计数器减1,让最终对象的引用计数指针指向新的引用计数空间

整体的代码结构:

// 深拷贝与浅拷贝相关代码
// An highlighted block
#include<iostream>
#include<string.h>

using namespace std;


//引用计数器类
class String_rep

	friend class String;
	friend ostream& operator<<(ostream &out, const String &s);
public:
	String_rep(const char *str = "") :use_count(0)
	
		m_data = new char[strlen(str) + 1];
		strcpy(m_data, str);
	
	String_rep(const String_rep &rep) :use_count(0)
	
		m_data = new char[strlen(rep.m_data) + 1];
		strcpy(m_data, rep.m_data);
	
	String_rep& operator=(const String_rep &rep);
	~String_rep()
	
		delete[]m_data;
		m_data = nullptr;
	
public:
	void increment()
	
		++use_count;
	
	void decrement()
	
		if (--use_count == 0)
		
			delete this; //自杀
		
	
private:
	char *m_data;
	size_t use_count;
;


/
class String

	friend ostream& operator<<(ostream &out, const String &s);
public:
	String(const char *str = "") :m_rep(new String_rep(str))
	
		m_rep->increment();
	
	String(const String &s) : m_rep(s.m_rep)
	
		m_rep->increment();//增加引用计数
	
	String& operator=(const String &s)
	
		if (this != &s)
		
			m_rep->decrement();
			m_rep = s.m_rep;
			m_rep->increment();
		
		return *this;
	
	~String()
	
		m_rep->decrement();//减少引用计数
	
public:
	//写时拷贝
	void to_upper()
	
		String_rep *new_rep = new String_rep(*m_rep);
		m_rep->decrement();
		m_rep = new_rep;

		char *pch = m_rep->m_data;
		while (*pch != '\\0')
		
			if (*pch >= 'a' && *pch <= 'z')
				*pch -= 32;
			pch++;
		
		m_rep->increment();
	
private:
	String_rep *m_rep;
;

ostream& operator<<(ostream &out, const String &s)

	out << s.m_rep->m_data;
	return out;


void main()

	String s1("abc");
	String s2 = s1;

	cout << "s1 = " << s1 << endl;
	cout << "s2 = " << s2 << endl;

	s1.to_upper();

	cout << "s1 = " << s1 << endl;
	cout << "s2 = " << s2 << endl;

/*
void main()

//String t("xyz");
String s("abc");
String s1 = s;

String s2("xyz");
s1 = s2;

以上是关于C++——浅拷贝深拷贝写时拷贝详解的主要内容,如果未能解决你的问题,请参考以下文章

C++——浅拷贝深拷贝写时拷贝详解

C++类的浅拷贝深拷贝以及写时拷贝问题

C++ 深浅拷贝写时拷贝

C++ 深浅拷贝写时拷贝

C++入门深拷贝和浅拷贝详解

C++入门深拷贝和浅拷贝详解