浅谈C++深、浅拷贝
什么是浅拷贝?
C++ 类如果没有自定义拷贝构造函数或者自定义赋值操作符而使用拷贝构造或者赋值操作符,这时候就会涉及到浅拷贝的问题。
浅拷贝导致的问题
浅拷贝遇到需要动态申请的内存时,只会将新的变量指针指向被拷贝的内存,导致两个变量同时管理一个内存,进而产生一系列问题:
1. 双重释放问题
原对象和被拷贝出来的对象离开生存空间时,双双调用析构函数释放同一块内存,会导致double free 问题;
double free 示例
#include<iostream>
#include<string>
#include<cstring>
class Person
{
public:
Person(const char *name)
{
if (name)
{
name_ = new char(strlen(name) + 1);
strcpy(name_,name);
}
};
//打印名字
void PrintName(){if(name_){std::cout << "name:" << name_ << std::endl;}};
~Person()
{
delete[] name_;//释放内存
std::cout << "Person destructor." << std::endl;
};
//需要动态申请的内存变量,但是没有自定义copy 构造函数和赋值运算符
private:
char *name_;
};
int main(int argc, char const *argv[])
{
Person person1("alang");
person1.PrintName();
Person person2(person1);//调用的是编译器自动生成的copy构造
person2.PrintName();
std::cout << "接下来开始调用析构函数回收内存" << std::endl;
return 0;
}
程序输出
name:alang
name:alang
接下来开始调用析构函数回收内存
Person destructor.
free(): double free detected in tcache 2
Aborted
2. 程序逻辑错误
当拷贝对象之一离开自己的生存空间时,会调用析构函数,会将同时管理的内存释放掉,导致其余对象去操作一个已经被释放的内存,同时也会存在双重释放问题
示例1:较上面只有main函数的更改
int main(int argc, char const *argv[])
{
Person person1("alang");
//大括号表示新的生存空间
{
Person person2(person1);//调用的是编译器自动生成的copy构造
person2.PrintName();
}
person1.PrintName();
std::cout << "接下来开始调用析构函数回收内存" << std::endl;
return 0;
}
最直接的示例是函数使用值传递的方式传递对象时,调用的是copy构造函数来传参的,这样导致函数运行结束之后,浅拷贝的内存被释放掉
这里的另一解决方案是:最好不要以值传递的形式传递函数形参,而是引用传递。
示例2:较上面只有main函数的更改
void dosomething(Person p){}
int main(int argc, char const *argv[])
{
Person person1("alang");
dosomething(person1);
person1.PrintName();
std::cout << "接下来开始调用析构函数回收内存" << std::endl;
return 0;
}
拒绝浅拷贝,使用深拷贝
在需要对动态内存操作时,不要使用编译器自动生成的拷贝构造函数和赋值运算符
这种情况下我们有两种方案:
- 直接禁用编译器自动生成的函数,直接不允许赋值操作;
- 自定义拷贝构造函数和赋值操作符。
这里给出两种方案的示例:
- 禁用,不是很推荐,因为这样丧失了程序的灵活性
Person(const Person &person) = delete;
Person& operator=(const Person &person) = delete;
- 完善自定义拷贝构造函数和自定义赋值操作符
#include<iostream>
#include<string>
#include<cstring>
class Person
{
public:
Person():name_(nullptr){std::cout << "Person default constructor." << std::endl;};
Person(const char *name)
{
if (name)
{
name_ = new char(strlen(name) + 1);
strcpy(name_,name);
}
};
//深拷贝构造函数
Person(const Person &person)
{
if (person.name_)
{
this->name_ = new char(strlen(person.name_) + 1);
strcpy(this->name_,person.name_);
}
};
//深拷贝赋值运算符
Person& operator=(const Person &person)
{
if (person.name_)
{
this->name_ = new char(strlen(person.name_) + 1);
strcpy(this->name_,person.name_);
}
return *this;
}
//打印名字
void PrintName(){if(name_){std::cout << "name:" << name_ << std::endl;}};
~Person()
{
delete[] name_;//释放内存
std::cout << "Person destructor." << std::endl;
};
//需要动态申请的内存变量,但是没有自定义copy 构造函数和赋值运算符
private:
char *name_;
};
int main(int argc, char const *argv[])
{
Person person1("alang");
person1.PrintName();
Person person2;
person2 = person1;
person2.PrintName();
return 0;
}