深拷贝与浅拷贝的区别
浅拷贝(默认拷贝函数):将原对象或原数组的引用直接赋给新对象,新数组,新对象/新数组只是原对象的一个引用。
深拷贝:创建一个新的对象和数组,将原对象的各项属性的“值”(数组的所有元素)拷贝过来,是“值”而不是引用
深拷贝会在堆内存中另外申请空间来储存数据,从而解决了指针悬挂问题。当数据成员中有指针时,必须要用深拷贝
浅拷贝
打断点 F11 进行调试
#include <iostream>
#include <string>
using namespace std;
class String
{
public:
String(const char *str="")
{
m_data = new char[strlen((str)+1)];
strcpy(m_data, str);
}
~String()
{
delete[]m_data;
m_data = nullptr;
}
private:
char* m_data;
};
void main()
{
String s1("abc");
String s2 = s1;
cout << "end";
}
可以看到s1->m_data与s2->m_data地址相同,他们的指针指向了同一地址空间,也就是说s2->m_data并没有自己的地址空间,s2->m_data指向了 s1->m_data地址空间,当 s1对象进行销毁时,会调用析构函数对s1在堆上的空间进行销毁,等到要销毁s2对象时其在堆上空间已经被释放,调用析构函数 会产生double free的错误;
深拷贝
深拷贝方法就是对以前的地址空间内容再进行拷贝一份申请了新的空间,使得拷贝的对象指向新的地址空间,这样就可以避免一个空间同时被析构两次的问题。
#include <iostream>
#include <string>
using namespace std;
class String
{
public:
String( const char *str="")
{
m_data = new char[strlen((str)+1)];
strcpy(m_data, str);
}
String(const String &s)
{
this->m_data = new char[strlen(s.m_data)+1];
strcpy(this->m_data, s.m_data);
}
String &operator=(const String &s)
{
if (this != &s)
{
this->m_data = new char[strlen(s.m_data) + 1];
strcpy(this->m_data, s.m_data);
}
return *this;
}
~String()
{
delete []m_data;
m_data = nullptr;
}
private:
char* m_data;
};
void main()
{
String s1("abc");
String s2;
s2 = s1;
cout << "end";
}
深浅/拷贝---好坏之分
深浅拷贝,并没有好坏之分,任何事物都要以辩证的视角来看,
以操作系统 读者写者角度来看,假设m_data是我们的资源和文件,以读者的身份其实我们不需要修改它,我们只要能进行阅读就可以了,这样我们只需要一个资源空间,便可以满足多个对象的访问。
在写者角度来看,则必须支持深拷贝,因为不深拷贝不但double free 而且会让别的对象的资源文件发生改变。
为此浅拷贝的引用计数也 由此而生
浅拷贝引用计数
操作系统 读写者问题 便可以采用引用计数,
创建一个文件只开放读权限,该资源允许所有对象去读,直到最后一个读者离开时,才释放资源。
采用引用计数
地址相同,且程序能够正常执行
#include<iostream>
#include<list>
#include<vector>
#include<string>
using namespace std;
class String;
//引用计数类
class String_rep
{
friend class String;
friend ostream& operator<<(ostream& out, const String& s);
public:
String_rep(const char* str = "") :m_count(0)
{
m_data = new char[strlen(str) + 1];
strcpy(m_data, str);
}
String_rep(const String_rep& rep) :m_data(nullptr), m_count(0)
{
String_rep _Tmp(rep.m_data);
swap(m_data, _Tmp.m_data);
}
String_rep& operator=(const String_rep& rep)
{
if (this != &rep)
{
String_rep _Tmp(rep);
swap(m_data, _Tmp.m_data);
}
return *this;
}
~String_rep()
{
delete[]m_data;
m_data = nullptr;
}
public:
void Increment()
{
m_count++;
}
void Decrement()
{
if (--m_count == 0)
{
delete this;
}
}
private:
char* m_data;
int m_count; //引用计数
};
class String
{
friend ostream& operator<<(ostream& out, const String& s);
public:
String(const char* str = "") :rep(new String_rep(str))
{
rep->Increment();
}
String(const String& s) :rep(s.rep)
{
rep->Increment();
}
String& operator=(const String& s)
{
if (this != &s)
{
rep->Decrement();
rep = s.rep;
rep->Increment();
}
return *this;
}
~String()
{
rep->Decrement();
}
public:
void to_upper()
{
//写时拷贝
String_rep* new_rep = new String_rep(rep->m_data);
rep->Decrement();
rep = new_rep;
rep->Increment();
char* pch = rep->m_data;
while (*pch != '\0')
{
if (*pch >= 'a' && *pch <= 'z')
*pch -= 32;
pch++;
}
}
private:
String_rep* rep;
};
ostream& operator<<(ostream& out, const String& s)
{
out << s.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;
}
这里this是谁?
自杀,防止了内存泄漏,其他对象析构的时候就会调用析构函数减少计数器数量,当最后一个对象析构的时候先调用自身析构方法,然后也紧接着要减少计数器,当等于0的时候,就delete this
会调用析构函数将自己所指的空间释放掉,然后在将this对象销毁,避免内存泄漏.
#include<iostream>
#include<list>
#include<vector>
#include<string>
using namespace std;
class String;
//引用计数类
class String_rep
{
friend class String;
friend ostream& operator<<(ostream& out, const String& s);
public:
String_rep(const char* str = "") :m_count(0)
{
m_data = new char[strlen(str) + 1];
strcpy(m_data, str);
}
String_rep(const String_rep& rep) :m_data(nullptr), m_count(0)
{
String_rep _Tmp(rep.m_data);
swap(m_data, _Tmp.m_data);
}
String_rep& operator=(const String_rep& rep)
{
if (this != &rep)
{
String_rep _Tmp(rep);
swap(m_data, _Tmp.m_data);
}
return *this;
}
~String_rep()
{
delete[]m_data;
m_data = nullptr;
}
public:
void Increment()
{
m_count++;
}
void Decrement()
{
if (--m_count == 0)
{
delete this;//自己干掉自己
}
}
private:
char* m_data;
int m_count; //引用计数
};
class String
{
friend ostream& operator<<(ostream& out, const String& s);
public:
String(const char* str = "") :rep(new String_rep(str))
{
rep->Increment();
}
String(const String& s) :rep(s.rep)
{
rep->Increment();
}
String& operator=(const String& s)
{
if (this != &s)
{
rep->Decrement();
rep = s.rep;
rep->Increment();
}
return *this;
}
~String()
{
rep->Decrement();
}
public:
void to_upper()
{
//写时拷贝
String_rep* new_rep = new String_rep(rep->m_data);//将资源拷贝过来
rep->Decrement();//由于要对资源变更,从旧的资源将指针移开,引用计数器--
rep = new_rep;//要变更的指针指向了拷贝的资源
rep->Increment();//拷贝的资源引用计数器++
char* pch = rep->m_data;//变更操作为转化为大写
while (*pch != '\0')
{
if (*pch >= 'a' && *pch <= 'z')
*pch -= 32;
pch++;
}
}
private:
String_rep* rep;
};
ostream& operator<<(ostream& out, const String& s)
{
out << s.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;
}
赋值操作的写法
s2对象给s1 赋值,s1所指向的空间计数器要减一,(s1指向了s2空间) s2空间计数器要++;
String& operator=(const String &s)
{
if(this != &s)
{
rep->Decrement();
rep = s.rep;
rep->Increment();
}
return *this;
}
写时拷贝
之前的对象共用一个资源,但是现在有对象期望对资源进行修改,我们应当具备这样的功能
在想进行修改时,将 源资源(最开始的资源) 拷贝一份给想修改资源的对象
void to_upper()
{
//写时拷贝
String_rep* new_rep = new String_rep(rep->m_data);//将资源拷贝过来
rep->Decrement();//由于要对资源变更,从旧的资源将指针移开,引用计数器--
rep = new_rep;//要变更的指针指向了拷贝的资源
rep->Increment();//拷贝的资源引用计数器++
char* pch = rep->m_data;//变更操作为转化为大写
while (*pch != '\0')
{
if (*pch >= 'a' && *pch <= 'z')
*pch -= 32;
pch++;
}
}