1、拷贝是什么

通俗的讲,拷贝就是再创建一个一摸一样的对象。

例如:
A b = a;

A b(a);

void foo(A x); // 会拷贝
foo(a);        // 产生拷贝

都会触发对象拷贝,默认会进行成员逐个拷贝(浅拷贝)

2、浅拷贝与深拷贝

浅拷贝:类似于复印一本书,只复印了封面,里面的内容没有复印,两个对象共用内容。所以任何一方修改内部内容,另一方也会受到影响。特别是delete的时候,会出错

深拷贝:封面和内容都复制一遍,两个对象互相不影响

例如:
class A {
public:
    int* p;
};

浅拷贝的情况下,两个对象指向一块内存。

  • double free(释放两次)
  • 修改A的p会影响B的p

3、拷贝构造函数

编译器默认是浅拷贝,出现的场景如下:

  • 用一个对象创建另一个对象
  • 函数传参(按值)
  • 函数返回对象(按值)

相当于新建时复制,示例代码如下:

格式:A(const A& other);

#include <iostream>
class MyData {
public:
    explicit MyData(const int x) {
        p = new int(x);
        std::cout<<"构造函数:分配内存,值="<<*p<<"\n";
    }
    MyData(const MyData &other) {
        p = new int(*other.p);
        std::cout<<"拷贝构造:深拷贝,值="<<*p<<"\n";
    }
    ~MyData() {
        delete p;
        std::cout<<"释放内存"<<"\n";
    }
    int* p;
};
int main() {
    MyData a(10);
    MyData b = a;
    std::cout<<"修改副本b.p的值\n";
    *b.p = 20;
    std::cout<<"a.p = "<<*a.p<<"\n";
    std::cout<<"b.p = "<<*b.p<<"\n";
}

4、复制赋值运算符

operator=用于现成对象被赋值。拷贝构造是又买了一本新书;赋值运算是把旧书的内容擦掉,重新写成另一本书的内容;让已经存在的对象把内容变成另外一个对象的内容。

格式:A& operator=(const A& other);

示例代码如下:

#include <iostream>
class MyData {
public:
    explicit MyData(const int x) {
        p = new int(x);
        std::cout<<"构造函数:分配内存,值="<<*p<<"\n";
    }

    MyData(const MyData &other) {
        p = new int(*other.p);
        std::cout<<"拷贝构造:深拷贝,值="<<*p<<"\n";
    }

    MyData& operator=(const MyData &other) {
        if (this == &other) { // 自赋值检查
            std::cout << "赋值运算符: 自赋值,跳过\n";
            return *this;
        }
        std::cout << "赋值运算符: 深拷贝, 新值 = " << *other.p << "\n";
        // 先释放旧资源
        delete p;
        // 分配新内存并复制值
        p = new int(*other.p);
        return *this;
    }

    ~MyData() {
        delete p;
        std::cout<<"释放内存"<<"\n";
    }
    int* p;
};
int main() {
    MyData a(10);   // 构造
    MyData b(20);   // 构造
    // 测试赋值运算符
    a = b;          // 深拷贝赋值
    std::cout << "赋值后: a.p = " << *a.p << ", b.p = " << *b.p << "\n";
    // 修改 b,不影响 a
    *b.p = 30;
    std::cout << "修改 b 后: a.p = " << *a.p << ", b.p = " << *b.p << "\n";
    // 测试自赋值
    a = a;
}

5、禁止拷贝(=delete)

有些类例如管理文件句柄的类;管理网络连接的类;管理互斥锁的类不允许被拷贝,可以按如下格式写:

ClassName(const ClassName&) = delete;
ClassName& operator=(const ClassName&) = delete;

6、Rule of Three(非常重要)

如果实现了以下三个中的任何一个,则必须实现另外两个:

  1. 析构函数
  2. 拷贝构造函数
  3. 复制赋值运算符(operator=)

否则会产生资源泄漏或者double free。

也就是说如果手动管理资源,必须告诉程序如何赋值和销毁。否则编译器默认浅拷贝会造成内存或资源问题。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐