C++ Copy and Swap

C++ learning

C++ Copy-and-Swap 详解和应用场景

一、什么是Copy-and-Swap?

Copy-and-Swap 是C++中一种实现赋值操作符的惯用法,它结合了拷贝构造函数和交换函数来实现强异常安全的赋值操作。这个惯用法的核心思想是:先创建一份副本,然后交换副本的内容,最后让原对象获得副本的资源

二、基本原理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <iostream>
#include <algorithm>  // for std::swap

class MyClass {
private:
    int* data;
    size_t size;

public:
    // 构造函数
    MyClass(size_t n = 0) : size(n), data(n ? new int[n] : nullptr) {
        std::cout << "Constructor\\n";
    }

    // 拷贝构造函数
    MyClass(const MyClass& other) : size(other.size),
                                    data(other.size ? new int[other.size] : nullptr) {
        std::cout << "Copy Constructor\\n";
        std::copy(other.data, other.data + size, data);
    }

    // 析构函数
    ~MyClass() {
        std::cout << "Destructor\\n";
        delete[] data;
    }

    // 交换函数 - 通常作为友元或成员函数
    friend void swap(MyClass& first, MyClass& second) noexcept {
        std::cout << "Swap\\n";
        using std::swap;
        swap(first.size, second.size);
        swap(first.data, second.data);
    }

    // 赋值操作符 - Copy-and-Swap 实现
    MyClass& operator=(MyClass other) {  // 注意:传值,不是传引用
        std::cout << "Assignment Operator\\n";
        swap(*this, other);  // 交换当前对象和副本
        return *this;
    }
};

int main() {
    MyClass a(10);  // Constructor
    MyClass b(5);   // Constructor

    a = b;  // 1. Copy Constructor (创建b的副本)
            // 2. Swap
            // 3. Destructor (销毁原来的a的资源)

    return 0;
}

三、Copy-and-Swap 的工作流程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
初始状态:
a: [data: 0x100, size: 10]
b: [data: 0x200, size: 5]

执行 a = b:

1. 传值参数 other 被拷贝构造(调用拷贝构造函数)
   other: [data: 0x300, size: 5]  (b的副本)

2. swap(*this, other) 交换 a 和 other
   a: [data: 0x300, size: 5]
   other: [data: 0x100, size: 10]

3. 函数结束,other 析构
   other 析构,释放原来的 a 的资源 (0x100)

结果:
a: [data: 0x300, size: 5]  (拥有b的副本)
b: [data: 0x200, size: 5]  (保持不变)

四、为什么需要Copy-and-Swap?

1. 异常安全

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include <iostream>
#include <stdexcept>

class UnsafeClass {
private:
    int* data;
    size_t size;
    int* metadata;  // 第二个资源

public:
    UnsafeClass(size_t n) : size(n),
                            data(new int[n]),
                            metadata(new int[100]) {}

    ~UnsafeClass() {
        delete[] data;
        delete[] metadata;
    }

    // 不安全的赋值操作符
    UnsafeClass& operator=(const UnsafeClass& other) {
        if (this != &other) {
            delete[] data;           // 第一步:释放原资源
            delete[] metadata;        // 第二步:释放原资源

            data = new int[other.size];  // 第三步:分配新资源
            metadata = new int[100];     // 第四步:分配新资源
            size = other.size;

            std::copy(other.data, other.data + size, data);
        }
        return *this;
    }
    // 问题:如果第三步 new 失败抛出异常,对象已经处于无效状态
    // data 和 metadata 已经被删除,无法恢复
};

class SafeClass {
private:
    int* data;
    size_t size;
    int* metadata;

public:
    SafeClass(size_t n) : size(n),
                          data(new int[n]),
                          metadata(new int[100]) {}

    ~SafeClass() {
        delete[] data;
        delete[] metadata;
    }

    SafeClass(const SafeClass& other) : size(other.size),
                                        data(new int[other.size]),
                                        metadata(new int[100]) {
        std::copy(other.data, other.data + size, data);
    }

    friend void swap(SafeClass& first, SafeClass& second) noexcept {
        using std::swap;
        swap(first.size, second.size);
        swap(first.data, second.data);
        swap(first.metadata, second.metadata);
    }

    // 异常安全的赋值操作符
    SafeClass& operator=(SafeClass other) noexcept {  // 注意:这里可以声明为noexcept
        swap(*this, other);
        return *this;
    }
    // 如果拷贝构造失败,异常会在进入函数前抛出
    // 原对象保持不变,完全符合强异常安全保证
};

2. 避免代码重复

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class ResourceManager {
private:
    Resource* res;

public:
    // 构造函数
    ResourceManager() : res(nullptr) {}

    // 拷贝构造函数
    ResourceManager(const ResourceManager& other)
        : res(other.res ? new Resource(*other.res) : nullptr) {}

    // 析构函数
    ~ResourceManager() { delete res; }

    // 交换函数
    friend void swap(ResourceManager& first, ResourceManager& second) noexcept {
        using std::swap;
        swap(first.res, second.res);
    }

    // 一个赋值操作符同时处理:
    // 1. 拷贝赋值
    // 2. 移动赋值(如果提供了移动构造函数)
    // 3. 不同类型赋值(通过隐式转换)
    ResourceManager& operator=(ResourceManager other) noexcept {
        swap(*this, other);
        return *this;
    }

    // 不需要单独实现移动赋值
    // 移动构造函数会自动被使用
    ResourceManager(ResourceManager&& other) noexcept
        : res(other.res) {
        other.res = nullptr;
    }
};

五、实际应用场景

场景1:管理动态数组的类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include <iostream>
#include <algorithm>
#include <cstring>

class DynamicArray {
private:
    int* arr;
    size_t len;

public:
    DynamicArray(size_t n = 0) : len(n), arr(n ? new int[n]() : nullptr) {
        std::cout << "DynamicArray(" << n << ")\\n";
    }

    // 初始化列表构造函数
    DynamicArray(std::initializer_list<int> list)
        : len(list.size()), arr(new int[list.size()]) {
        std::copy(list.begin(), list.end(), arr);
    }

    // 拷贝构造函数
    DynamicArray(const DynamicArray& other)
        : len(other.len), arr(other.len ? new int[other.len] : nullptr) {
        std::copy(other.arr, other.arr + len, arr);
        std::cout << "Copy constructor\\n";
    }

    // 移动构造函数
    DynamicArray(DynamicArray&& other) noexcept
        : arr(other.arr), len(other.len) {
        other.arr = nullptr;
        other.len = 0;
        std::cout << "Move constructor\\n";
    }

    ~DynamicArray() {
        delete[] arr;
    }

    // 交换函数
    friend void swap(DynamicArray& first, DynamicArray& second) noexcept {
        using std::swap;
        swap(first.arr, second.arr);
        swap(first.len, second.len);
    }

    // Copy-and-Swap 赋值操作符
    DynamicArray& operator=(DynamicArray other) noexcept {
        swap(*this, other);
        return *this;
    }

    void print() const {
        std::cout << "[";
        for (size_t i = 0; i < len; ++i) {
            std::cout << arr[i] << (i < len - 1 ? ", " : "");
        }
        std::cout << "]\\n";
    }
};

int main() {
    DynamicArray arr1 = {1, 2, 3, 4, 5};
    DynamicArray arr2 = {6, 7, 8};

    std::cout << "Before assignment:\\n";
    arr1.print();  // [1, 2, 3, 4, 5]
    arr2.print();  // [6, 7, 8]

    arr1 = arr2;   // 拷贝赋值

    std::cout << "After assignment:\\n";
    arr1.print();  // [6, 7, 8]
    arr2.print();  // [6, 7, 8]

    DynamicArray arr3 = {9, 10, 11, 12};
    arr1 = std::move(arr3);  // 移动赋值

    std::cout << "After move:\\n";
    arr1.print();  // [9, 10, 11, 12]
    arr3.print();  // [] (arr3被移动)

    return 0;
}

场景2:智能指针实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <iostream>
#include <atomic>

template<typename T>
class SharedPointer {
private:
    T* ptr;
    std::atomic<int>* ref_count;

public:
    // 构造函数
    explicit SharedPointer(T* p = nullptr)
        : ptr(p), ref_count(p ? new std::atomic<int>(1) : nullptr) {}

    // 拷贝构造函数
    SharedPointer(const SharedPointer& other) noexcept
        : ptr(other.ptr), ref_count(other.ref_count) {
        if (ref_count) {
            (*ref_count)++;
        }
    }

    // 移动构造函数
    SharedPointer(SharedPointer&& other) noexcept
        : ptr(other.ptr), ref_count(other.ref_count) {
        other.ptr = nullptr;
        other.ref_count = nullptr;
    }

    // 析构函数
    ~SharedPointer() {
        release();
    }

    // 交换函数
    friend void swap(SharedPointer& first, SharedPointer& second) noexcept {
        using std::swap;
        swap(first.ptr, second.ptr);
        swap(first.ref_count, second.ref_count);
    }

    // Copy-and-Swap 赋值操作符
    SharedPointer& operator=(SharedPointer other) noexcept {
        swap(*this, other);
        return *this;
    }

private:
    void release() {
        if (ref_count) {
            if (--(*ref_count) == 0) {
                delete ptr;
                delete ref_count;
            }
        }
    }

public:
    T& operator*() const { return *ptr; }
    T* operator->() const { return ptr; }

    int use_count() const { return ref_count ? ref_count->load() : 0; }
};

场景3:资源管理类(文件句柄)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <iostream>
#include <fstream>
#include <string>

class FileHandle {
private:
    FILE* file;
    std::string filename;

public:
    FileHandle(const std::string& name, const char* mode)
        : filename(name), file(fopen(name.c_str(), mode)) {
        if (!file) {
            throw std::runtime_error("Cannot open file: " + name);
        }
    }

    // 拷贝构造函数(创建新文件句柄,指向同一个文件)
    FileHandle(const FileHandle& other)
        : filename(other.filename), file(fopen(other.filename.c_str(), "r+")) {
        if (!file) {
            throw std::runtime_error("Cannot open file: " + filename);
        }
        // 复制文件位置等
    }

    // 移动构造函数
    FileHandle(FileHandle&& other) noexcept
        : file(other.file), filename(std::move(other.filename)) {
        other.file = nullptr;
    }

    ~FileHandle() {
        if (file) {
            fclose(file);
        }
    }

    // 交换函数
    friend void swap(FileHandle& first, FileHandle& second) noexcept {
        using std::swap;
        swap(first.file, second.file);
        swap(first.filename, second.filename);
    }

    // Copy-and-Swap 赋值操作符
    FileHandle& operator=(FileHandle other) noexcept {
        swap(*this, other);
        return *this;
    }

    void write(const std::string& data) {
        if (file) {
            fwrite(data.c_str(), 1, data.size(), file);
        }
    }
};

六、Copy-and-Swap vs 传统实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 传统实现方式
class Traditional {
    Resource* res;

public:
    Traditional& operator=(const Traditional& other) {
        if (this != &other) {           // 需要自赋值检查
            delete res;                   // 释放原资源
            res = new Resource(*other.res); // 分配新资源
        }
        return *this;
    }
    // 问题:如果new抛出异常,对象处于无资源状态
    // 需要单独实现移动赋值
};

// Copy-and-Swap实现
class Modern {
    Resource* res;

public:
    Modern& operator=(Modern other) {   // 不需要自赋值检查
        swap(*this, other);              // 异常安全的交换
        return *this;
    }
    // 优点:
    // 1. 强异常安全保证
    // 2. 同时处理拷贝赋值和移动赋值
    // 3. 代码简洁
};

七、性能考虑

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <chrono>
#include <vector>

class SmallObject {
    int data[10];  // 小对象

public:
    SmallObject() = default;

    // Copy-and-Swap
    SmallObject& operator=(SmallObject other) {
        swap(*this, other);
        return *this;
    }

    friend void swap(SmallObject& a, SmallObject& b) {
        using std::swap;
        for (int i = 0; i < 10; ++i) {
            swap(a.data[i], b.data[i]);
        }
    }
};

class LargeObject {
    std::vector<int> data;  // 大对象,管理堆内存

public:
    LargeObject() = default;

    // Copy-and-Swap (适合管理资源的类)
    LargeObject& operator=(LargeObject other) {
        swap(*this, other);
        return *this;
    }

    friend void swap(LargeObject& a, LargeObject& b) noexcept {
        using std::swap;
        swap(a.data, b.data);  // 只交换指针,高效
    }
};

// 性能建议:
// 1. 对于管理资源的类(RAII类),Copy-and-Swap是最佳选择
// 2. 对于简单的值类型,传统方式可能更高效
// 3. 考虑使用移动语义优化性能

八、最佳实践和注意事项

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class BestPractices {
private:
    int* data;
    size_t size;

public:
    // 1. 提供noexcept的swap函数
    friend void swap(BestPractices& a, BestPractices& b) noexcept {
        using std::swap;
        swap(a.data, b.data);
        swap(a.size, b.size);
    }

    // 2. 赋值操作符参数传值,返回引用
    BestPractices& operator=(BestPractices other) noexcept {
        swap(*this, other);
        return *this;
    }

    // 3. 移动构造函数应该也声明为noexcept
    BestPractices(BestPractices&& other) noexcept
        : data(other.data), size(other.size) {
        other.data = nullptr;
        other.size = 0;
    }

    // 4. 如果类可能被继承,考虑protected或virtual析构函数
    virtual ~BestPractices() = default;
};

// 注意事项:
// 1. 确保swap函数不会抛出异常
// 2. 赋值操作符参数类型要与类类型匹配
// 3. 考虑使用final关键字防止继承时的 slicing 问题

九、总结

Copy-and-Swap 的核心优势

  1. 强异常安全保证:要么赋值成功,要么对象保持不变
  2. 代码简洁:一个赋值操作符处理多种情况
  3. 避免代码重复:复用拷贝构造函数和析构函数
  4. 自赋值安全:不需要显式检查自赋值

适用场景

  • 管理动态资源的RAII类
  • 需要强异常安全的类
  • 有多个资源的类
  • 需要同时支持拷贝和移动赋值的类

不适用场景

  • 简单的值类型(POD类型)
  • 性能极其关键的代码
  • 不需要异常安全的场景

最佳实践:在管理资源的类中优先考虑Copy-and-Swap惯用法,它提供了简洁、安全、易维护的赋值操作符实现。

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy