C++深拷贝与浅拷贝

时间:2022-06-22 22:23:27

一、浅拷贝及其不足

对于基本类型数据以及简单的对象,它们的拷贝比较简单,就是直接复制内存。比如下面的代码:

class Base{
public:
    Base(): m_a(0), m_b(0){ }
    Base(int a, int b): m_a(a), m_b(b){ }
private:
    int m_a;
    int m_b;
};
int main(){
    int a = 10;
    int b = a;  //拷贝
    Base obj1(10, 20);
    Base obj2 = obj1;  //拷贝
    return 0;
}

b 和 obj2 都是以拷贝的方式初始化的,具体来说,就是将 a 和 obj1 所在内存中的数据按照二进制位(Bit)复制到 b 和 obj2 所在的内存,这种默认的拷贝行为就是浅拷贝,这和调用memcpy() 很类似。但是,当类的成员包含指针的时候,使用浅拷贝就不能满足实际要求了,来看下面的例子。

#include <iostream>
#include<string>
using namespace std;

class People
{
public:
    People(string name, int* ptr);  // 普通构造函数,不显示声明拷贝构造函数
    ~People();
    void Display();
    void SetAge(int age);
private:
    string m_name;
    int* mp_age;

};

People::People(string name, int* ptr)
{
    m_name = name;
    mp_age = ptr;
}

People::~People()
{
    
}

void People::Display()
{
    cout << m_name <<" is age "<< *mp_age << endl;
}

void People::SetAge(int age)
{
    *mp_age = age;
}

int main()
{
    int* ptr = new int(10);
    string name = "Xiao Ming";
    People people1 = People(name, ptr);  //调用普通构造函数
    People people2(people1);  //调用默认拷贝构造函数

    people1.Display();
    people2.Display();

    people1.SetAge(15);   // 修改 people1 age
    
    people1.Display();
    people2.Display();

    return 0;
}

/*
输出:
Xiao Ming is age 10
Xiao Ming is age 10
Xiao Ming is age 15
Xiao Ming is age 15   //修改 people1 age 之后 people2 age 也被修改了

 */

​ 看上面的例子修改 people1 age 之后 people2 age 也被修改了,这是因为mp_age 是一个指针,里面存放的是指向存储 age 内容的地址,使用默认拷贝构造函数时这是把 people1的 mp_age指针里存放的地址赋值给了people2的mp_age指针导致两个指针指向了同一块内存空间,这时候默认拷贝构造函数的不足就体现出来了。

二、显示定义拷贝构造函数,使用深拷贝

​ 对于简单的类,默认的拷贝构造函数一般就够用了,我们也没有必要再显式地定义一个功能类似的拷贝构造函数。但是当类持有其它资源时,例如动态分配的内存、指向其他数据的指针等,默认的拷贝构造函数就不能拷贝这些资源了,我们必须显式地定义拷贝构造函数,以完整地拷贝对象的所有数据。下面我们显示定义一个拷贝构造函数来弥补默认拷贝构造函数的不足,代码如下:

#include <iostream>
#include<string>
using namespace std;

class People
{
public:
    People(string name, int* ptr);  // 普通构造函数,
    People(const People &peo);   //显示声明拷贝构造函数
    ~People();
    void Display();
    void SetAge(int age);
private:
    string m_name;
    int* mp_age;

};

People::People(string name, int* ptr)
{
    m_name = name;
    mp_age = ptr;
}

People::People(const People &peo)
{
    this->m_name = peo.m_name;
    this->mp_age = new int(*peo.mp_age); //重新申请一块内存来存放 age,避免两个对象使用同一块内存
}

People::~People()
{
    //释放内存,防止内存泄漏
    delete mp_age;  
    mp_age = NULL;
}

void People::Display()
{
    cout << m_name <<" is age "<< *mp_age << endl;
}

void People::SetAge(int age)
{
    *mp_age = age;
}

int main()
{
    int* ptr = new int(10);
    string name = "Xiao Ming";
    People people1 = People(name, ptr);
    People people2(people1);  //调用拷贝构造函数

    people1.Display();
    people2.Display();

    people1.SetAge(15);  // 修改 people1 age
    
    people1.Display();
    people2.Display();

    return 0;
}

/*
输出:
Xiao Ming is age 10
Xiao Ming is age 10
Xiao Ming is age 15
Xiao Ming is age 10  //修改 people1 age 之后 people2 age 没有被修改

 */

​ 我们显式地定义了拷贝构造函数,它除了会将原有对象的所有成员变量拷贝给新对象,还会为新对象再分配一块内存,并将原有对象所持有的内存也拷贝过来。这样做的结果是,原有对象和新对象所持有的动态内存是相互独立的,更改一个对象的数据不会影响另外一个对象。

这种将对象所持有的其它资源一起拷贝的行为叫做深拷贝,必须显示的定义拷贝构造函数才能达到深拷贝的目的。

​ 如果一个类拥有指针类型的成员变量,那么绝大部分情况下就需要深拷贝,因为只有这样,才能将指针指向的内容再复制出一份来,让原有对象和新生对象相互独立,彼此之间不受影响。如果类的成员变量没有指针,一般浅拷贝就能满足要求。另外一种需要深拷贝的情况就是在创建对象时进行一些预处理工作。