《C++ Primer》导学系列:第 13 章 - 拷贝控制

13.1 拷贝、赋值与析构函数

拷贝控制是C++中类设计的重要组成部分,用于管理对象的复制、赋值和销毁过程。理解并正确实现拷贝控制函数(拷贝构造函数、拷贝赋值运算符和析构函数)对于编写健壮和高效的C++程序至关重要。

13.1.1 拷贝构造函数

拷贝构造函数用于创建对象的副本。它的声明形式如下:

ClassName(const ClassName &other);

当对象被复制时,拷贝构造函数会被调用。例如,当对象以值传递的方式作为参数传递或从函数返回时,拷贝构造函数将被调用。

示例代码

#include <iostream>

class Example {
public:
    Example(int v) : value(v) {}
    Example(const Example &other) : value(other.value) {
        std::cout << "Copy constructor called" << std::endl;
    }
    int getValue() const { return value; }
private:
    int value;
};

void printExample(Example e) {
    std::cout << "Value: " << e.getValue() << std::endl;
}

int main() {
    Example ex1(10);
    Example ex2 = ex1;  // 调用拷贝构造函数
    printExample(ex1);  // 调用拷贝构造函数
    return 0;
}

13.1.2 拷贝赋值运算符

拷贝赋值运算符用于将一个对象的值赋给另一个对象。它的声明形式如下:

ClassName& operator=(const ClassName &other);

拷贝赋值运算符通常用于对象已经存在的情况下进行赋值操作。

示例代码

#include <iostream>

class Example {
public:
    Example(int v) : value(v) {}
    Example(const Example &other) : value(other.value) {
        std::cout << "Copy constructor called" << std::endl;
    }
    Example& operator=(const Example &other) {
        if (this != &other) {
            value = other.value;
            std::cout << "Copy assignment operator called" << std::endl;
        }
        return *this;
    }
    int getValue() const { return value; }
private:
    int value;
};

int main() {
    Example ex1(10);
    Example ex2(20);
    ex2 = ex1;  // 调用拷贝赋值运算符
    std::cout << "ex2 value: " << ex2.getValue() << std::endl;
    return 0;
}

13.1.3 析构函数

析构函数用于释放对象使用的资源。它的声明形式如下:

~ClassName();

析构函数在对象的生命周期结束时被自动调用,例如当对象超出作用域或被显式删除时。

示例代码

#include <iostream>

class Example {
public:
    Example(int v) : value(v) {}
    ~Example() {
        std::cout << "Destructor called" << std::endl;
    }
    int getValue() const { return value; }
private:
    int value;
};

int main() {
    Example ex1(10);
    std::cout << "ex1 value: " << ex1.getValue() << std::endl;
    return 0;
}

13.1.4 三/五法则

C++中有一个重要的设计原则,称为“三/五法则”,意思是如果一个类定义了析构函数、拷贝构造函数或拷贝赋值运算符中的一个,那么它很可能需要定义所有这三个函数。随着C++11标准引入移动语义,这个原则扩展为“五法则”,包括移动构造函数和移动赋值运算符。

示例代码

#include <iostream>
#include <utility>

class Example {
public:
    Example(int v) : value(new int(v)) {}
    Example(const Example &other) : value(new int(*other.value)) {
        std::cout << "Copy constructor called" << std::endl;
    }
    Example& operator=(const Example &other) {
        if (this != &other) {
            delete value;
            value = new int(*other.value);
            std::cout << "Copy assignment operator called" << std::endl;
        }
        return *this;
    }
    ~Example() {
        delete value;
        std::cout << "Destructor called" << std::endl;
    }

    // C++11移动构造函数
    Example(Example &&other) noexcept : value(other.value) {
        other.value = nullptr;
        std::cout << "Move constructor called" << std::endl;
    }

    // C++11移动赋值运算符
    Example& operator=(Example &&other) noexcept {
        if (this != &other) {
            delete value;
            value = other.value;
            other.value = nullptr;
            std::cout << "Move assignment operator called" << std::endl;
        }
        return *this;
    }

    int getValue() const { return *value; }
private:
    int* value;
};

int main() {
    Example ex1(10);
    Example ex2 = std::move(ex1);  // 调用移动构造函数
    Example ex3(20);
    ex3 = std::move(ex2);  // 调用移动赋值运算符
    return 0;
}

13.1.5 阻止拷贝

在某些情况下,我们可能希望阻止对象被拷贝或赋值。可以通过将拷贝构造函数和拷贝赋值运算符声明为delete来实现这一点。

示例代码

class NonCopyable {
public:
    NonCopyable() = default;
    NonCopyable(const NonCopyable &) = delete;
    NonCopyable& operator=(const NonCopyable &) = delete;
};

int main() {
    NonCopyable obj1;
    // NonCopyable obj2 = obj1;  // 错误:拷贝构造函数被删除
    // NonCopyable obj3;
    // obj3 = obj1;  // 错误:拷贝赋值运算符被删除
    return 0;
}

重点与难点分析

重点

  1. 拷贝构造函数:了解何时会调用拷贝构造函数,掌握其实现方法。
  2. 拷贝赋值运算符:理解拷贝赋值运算符的使用场景和实现细节。
  3. 析构函数:熟悉析构函数的作用和调用时机。
  4. 三/五法则:掌握何时需要同时实现拷贝构造函数、拷贝赋值运算符和析构函数,理解移动语义下的五法则。
  5. 阻止拷贝:了解如何通过delete关键字阻止对象的拷贝和赋值。

难点

  1. 自我赋值检测:在拷贝赋值运算符中处理自我赋值的情况。
  2. 资源管理:确保在析构函数中正确释放资源,避免内存泄漏和其他资源管理问题。
  3. 编写健壮的拷贝控制函数:理解并正确实现拷贝控制函数,以确保对象在复制、赋值和销毁过程中行为一致且安全。
  4. 实现移动语义:正确实现移动构造函数和移动赋值运算符,提高程序性能。

练习题解析

  1. 练习13.1:编写一个类,包含拷贝构造函数、拷贝赋值运算符和析构函数,测试它们的调用情况。
    • 示例代码
#include <iostream>

class Example {
public:
    Example(int v) : value(v) {}
    Example(const Example &other) : value(other.value) {
        std::cout << "Copy constructor called" << std::endl;
    }
    Example& operator=(const Example &other) {
        if (this != &other) {
            value = other.value;
            std::cout << "Copy assignment operator called" << std::endl;
        }
        return *this;
    }
    ~Example() {
        std::cout << "Destructor called" << std::endl;
    }
    int getValue() const { return value; }
private:
    int value;
};

int main() {
    Example ex1(10);
    Example ex2 = ex1;  // 调用拷贝构造函数
    ex2 = ex1;          // 调用拷贝赋值运算符
    return 0;
}
  1. 练习13.2:修改练习13.1的类,使其包含一个指向动态内存的指针,确保拷贝控制函数正确管理该内存。
    • 示例代码
#include <iostream>

class Example {
public:
Example(int v) : value(new int(v)) {}
Example(const Example &other) : value(new int(*other.value)) {
    std::cout << "Copy constructor called" << std::endl;
}
Example& operator=(const Example &other) {
    if (this != &other) {
        delete value;
        value = new int(*other.value);
        std::cout << "Copy assignment operator called" << std::endl;
    }
    return *this;
}
~Example() {
    delete value;
    std::cout << "Destructor called" << std::endl;
}
int getValue() const { return *value; }
private:
int* value;
};

int main() {
    Example ex1(10);
    Example ex2 = ex1;  // 调用拷贝构造函数
    ex2 = ex1;          // 调用拷贝赋值运算符
    std::cout << "ex2 value: " << ex2.getValue() << std::endl;
    return 0;
}
  1. 练习13.3:编写一个类,包含移动构造函数和移动赋值运算符,测试它们的调用情况。
    • 示例代码
#include <iostream>

class Example {
public:
    Example(int v) : value(new int(v)) {}
    Example(const Example &other) : value(new int(*other.value)) {
        std::cout << "Copy constructor called" << std::endl;
    }
    Example& operator=(const Example &other) {
        if (this != &other) {
            delete value;
            value = new int(*other.value);
            std::cout << "Copy assignment operator called" << std::endl;
        }
        return *this;
    }
    Example(Example &&other) noexcept : value(other.value) {
        other.value = nullptr;
        std::cout << "Move constructor called" << std::endl;
    }
    Example& operator=(Example &&other) noexcept {
        if (this != &other) {
            delete value;
            value = other.value;
            other.value = nullptr;
            std::cout << "Move assignment operator called" << std::endl;
        }
        return *this;
    }
    ~Example() {
        delete value;
        std::cout << "Destructor called" << std::endl;
    }
    int getValue() const { return *value; }
private:
    int* value;
};

int main() {
    Example ex1(10);
    Example ex2 = std::move(ex1);  // 调用移动构造函数
    Example ex3(20);
    ex3 = std::move(ex2);          // 调用移动赋值运算符
    return 0;
}
  1. 练习13.4:编写一个类,阻止其对象的拷贝和赋值。
    • 示例代码
#include <iostream>

class NonCopyable {
public:
    NonCopyable() = default;
    NonCopyable(const NonCopyable &) = delete;
    NonCopyable& operator=(const NonCopyable &) = delete;
};

int main() {
    NonCopyable obj1;
    // NonCopyable obj2 = obj1;  // 错误:拷贝构造函数被删除
    // NonCopyable obj3;
    // obj3 = obj1;  // 错误:拷贝赋值运算符被删除
    return 0;
}

总结与提高

本节总结

  1. 掌握了拷贝控制的基本概念和操作,包括拷贝构造函数、拷贝赋值运算符和析构函数。
  2. 理解了拷贝控制函数在对象复制、赋值和销毁过程中的作用和调用时机。
  3. 学会了在类中正确实现拷贝控制函数,以确保对象在不同生命周期阶段的正确行为。
  4. 理解了“三/五法则”的重要性,学会了在类设计中同时实现必要的拷贝控制函数和移动语义。
  5. 掌握了如何通过delete关键字阻止对象的拷贝和赋值。

提高建议

  1. 多练习拷贝控制函数的实现:通过编写更多涉及拷贝控制的类,熟悉各种管理方法的用法,提高对拷贝控制的理解和实现能力。
  2. 深入理解资源管理的原理:通过阅读文档和相关书籍,深入理解资源管理的实现原理和使用场景,提高编写高效代码的能力。
  3. 优先使用智能指针:在实际项目中,尽量使用智能指针管理动态内存,以减少手动内存管理带来的错误,提高代码的可读性和可维护性。
  4. 优化移动语义:在类设计中,合理运用移动构造函数和移动赋值运算符,提高程序的性能和资源利用效率。

13.2 拷贝控制与资源管理

拷贝控制函数在管理资源时非常重要,特别是当类涉及动态内存分配或其他资源(如文件句柄、网络连接等)时。通过正确实现拷贝控制函数,可以确保资源在对象复制、赋值和销毁过程中得到正确的管理和释放。

13.2.1 拷贝控制与动态内存

在涉及动态内存的类中,拷贝构造函数、拷贝赋值运算符和析构函数的实现需要特别注意,以确保内存安全。

示例代码

#include <iostream>
#include <algorithm> // for std::copy

class DynamicArray {
public:
    DynamicArray(size_t size) : size(size), data(new int[size]()) {}
    DynamicArray(const DynamicArray &other) : size(other.size), data(new int[other.size]) {
        std::copy(other.data, other.data + other.size, data);
        std::cout << "Copy constructor called" << std::endl;
    }
    DynamicArray& operator=(const DynamicArray &other) {
        if (this != &other) {
    
  • 12
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

iShare_爱分享

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值