一、深拷贝VS浅拷贝
面试官总喜欢让面试者实现一个string类,最主要实现的string类的构造、拷贝构造、赋值运算符重载以及析构函数。但是如果我们只实现一部分功能,其它采用编译器默认实现的功能,就涉及到了深浅拷贝问题,如下我们实现一段string代码
//string浅拷贝
namespace leon
{
class string
{
public:
//string构造函数
string(const char* str = "")
{
assert(str); //如果str为nullptr直接报错
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
~string()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char *_str;
};
}
void Teststring1()
{
leon::string s1("hello");
leon::string s2(s1);
}
1.1 浅拷贝
上述string类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认的,当用s1构造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内存空间,在释放时同一块
空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝 。
Q1:什么是浅拷贝?
当一个对象构造另一个对象(一个对象给另一个对象赋值时),后者的指针类型成员指向前者指针类型成员指向的空间,当析构这两个对象时,会对同一块空间重复释放。
Q2:什么是深拷贝?
当一个对象构造另一个对象(一个对象给另一个对象赋值的时候),被赋值对象的指针类型成员指向新开辟的一块空间,新空间指向的内容和赋值对象指向的内容相同。
解决浅拷贝一种方式就是引入深拷贝。
1.2 深拷贝
如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。
我们显式定义拷贝构造函数
string(const string& s)
{
_str = new char[strlen(s._str) + 1];
strcpy(_str, s._str);
}
<1> 模拟实现string(传统写法)
class string {
public:
string(const char* str = "")
{
if (nullptr == str)
{
assert(false);
return;
}
//开辟和str一样的空间,并拷贝赋值,strcpy会拷贝'\0'
_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 (this != &s)
{
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(现代写法)
class string {
public:
string(const char* str = "")
{
if (nullptr == str)
{
assert(false);
return;
}
//开辟和str一样的空间,并拷贝赋值,strcpy会拷贝'\0'
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
string(const string& s)
{
string strtmp(s._str);
std::swap(_str, strtmp._str);
}
string& operator=(string s)
{
std::swap(_str, s._str);
return *this;
}
~string()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};