乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • C++ 学习路径
  • 第1章 C++概述与开发环境

    • C++ 语言概述与编译模型
    • 第一个 C++ 程序与规范
    • 开发环境配置
    • 预处理指令详解
  • 第2章 基本语法与类型系统

    • 变量与基本类型
    • 类型转换
    • 枚举类型
    • 引用与指针
    • 数组与 stdarray
    • 字符串与原始字符串字面量
    • const 与 constexpr
    • nullptr 与空指针
    • 类型推导 auto 与 decltype
    • 基于范围的 for 循环
    • 值类别全面解析
    • char16_t 与 char32_t
    • static_assert 编译期断言
  • 第3章 函数与重载

    • 函数声明与定义
    • 函数重载
    • 默认参数与内联函数
    • Lambda 表达式
    • 函数对象与 stdfunction
    • 后置返回类型与 noexcept
  • 第4章 类与对象

    • 类的基本定义
    • 构造函数与析构函数
    • 拷贝控制
    • 移动构造函数与移动赋值
    • 列表初始化与类内初始化器
    • 静态成员与嵌套类
    • 友元
    • =default 与 =delete
  • 第5章 继承与多态

    • 继承基础
    • 虚函数与多态
    • 虚函数表与动态绑定原理
    • 虚析构函数
    • 抽象类与纯虚函数
    • 多重继承与虚继承
    • 继承构造函数
  • 第6章 运算符重载

    • 运算符重载基础
    • 算术与关系运算符重载
    • 赋值与移动运算符重载
    • 特殊运算符重载
  • 第7章 模板与泛型编程

    • 函数模板
    • 类模板
    • 模板特化与偏特化
    • 可变参数模板
    • 别名模板与模板模板参数
    • SFINAE 与类型萃取
    • 依赖名与 typename/template 关键字
  • 第8章 异常处理

    • 异常处理机制
    • noexcept 与异常安全
    • RAII 与异常安全实践
  • 第9章 内存管理与智能指针

    • 动态内存与内存分区
    • RAII 惯用法
    • unique_ptr
    • shared_ptr 与 weak_ptr
    • 内存管理最佳实践
  • 第10章 右值引用与移动语义

    • 右值引用与值类别深度解析
    • std::move 原理与使用
    • 完美转发与 std::forward
    • 移动语义性能对比与最佳实践
  • 第11章 STL容器

    • vector 深度剖析
    • deque 内部机制
    • list 与 forward_list
    • map 与 set 深度解析
    • unordered 容器与哈希原理
    • array 与 tuple
    • 容器适配器
    • 容器选择全景指南
  • 第12章 STL算法与迭代器

    • 迭代器体系全解
    • 非变异算法
    • 变异算法
    • 排序与二分算法
    • Lambda 与算法组合
    • std::random 随机数库
    • 自定义迭代器开发
  • 第13章 IO流与文件

    • 标准 IO 流
    • 格式化输出控制
    • 文件流操作
    • 字符串流
    • std::regex 正则表达式
  • 第14章 并发与多线程

    • thread 基础与线程管理
    • mutex 与 lock_guard
    • unique_lock 与 condition_variable
    • thread_local 线程局部存储
    • atomic 与内存序
    • future 与 async 异步编程
    • std::chrono 时间库
  • 第15章 现代C++新特性

    • 从 C++11 到 C++20 演进路线
    • C++14 关键新特性
    • C++17 关键新特性
    • C++20 核心特性速览
  • 第16章 面试考点与最佳实践

    • C++ 综合最佳实践清单
    • 高频面试题精讲
    • 多线程面试题与实战
    • 内存管理常见陷阱与排查
  • 附录

    • C++ 核心知识点
    • C++ 专业术语

赋值与移动运算符重载

定义与作用

赋值运算符(=)和移动赋值运算符是类资源管理的核心。正确的赋值实现必须处理三个关键问题:

  1. 自赋值安全:a = a 时不会破坏自身状态
  2. 异常安全:操作失败时对象仍处于有效状态
  3. 资源管理:旧资源正确释放,新资源正确接管
MyClass& operator=(const MyClass& other);   // 拷贝赋值
MyClass& operator=(MyClass&& other);        // 移动赋值(C++11)

核心原理

copy-and-swap 惯用法

赋值运算符的实现层次

完整示例

示例一:动态数组类的完整赋值体系

场景说明:手写动态数组类,展示拷贝赋值、移动赋值和 copy-and-swap。

#include <iostream>
#include <algorithm>
#include <utility>

template <typename T>
class DynamicArray {
public:
    DynamicArray() : data_(nullptr), size_(0), capacity_(0) {}

    explicit DynamicArray(size_t size)
        : data_(new T[size]{})
        , size_(size)
        , capacity_(size) {}

    DynamicArray(const DynamicArray& other)
        : data_(new T[other.capacity_])
        , size_(other.size_)
        , capacity_(other.capacity_) {
        std::copy(other.data_, other.data_ + other.size_, data_);
        std::cout << "  [拷贝构造] size=" << size_ << std::endl;
    }

    DynamicArray(DynamicArray&& other) noexcept
        : data_(other.data_)
        , size_(other.size_)
        , capacity_(other.capacity_) {
        other.data_ = nullptr;
        other.size_ = 0;
        other.capacity_ = 0;
        std::cout << "  [移动构造] size=" << size_ << std::endl;
    }

    ~DynamicArray() {
        delete[] data_;
        std::cout << "  [析构]" << std::endl;
    }

    // ====== 拷贝赋值(逐个成员实现,需处理自赋值) ======
    // 方案 A:传统写法
    /*
    DynamicArray& operator=(const DynamicArray& other) {
        if (this != &other) {              // 自赋值检查
            T* new_data = new T[other.capacity_];
            std::copy(other.data_, other.data_ + other.size_, new_data);
            delete[] data_;
            data_ = new_data;
            size_ = other.size_;
            capacity_ = other.capacity_;
        }
        return *this;
    }
    */

    // ====== 拷贝赋值(copy-and-swap 惯用法,更优雅) ======
    DynamicArray& operator=(const DynamicArray& other) {
        DynamicArray temp(other);          // 拷贝构造(可能抛异常)
        swap(temp);                         // 交换(不抛异常)
        std::cout << "  [拷贝赋值]" << std::endl;
        return *this;
    }

    // ====== 移动赋值 ======
    DynamicArray& operator=(DynamicArray&& other) noexcept {
        if (this != &other) {
            delete[] data_;               // 释放自己的旧资源
            data_ = other.data_;          // 接管对方的资源
            size_ = other.size_;
            capacity_ = other.capacity_;
            other.data_ = nullptr;        // 对方置空
            other.size_ = 0;
            other.capacity_ = 0;
        }
        std::cout << "  [移动赋值]" << std::endl;
        return *this;
    }

    // ====== 统一赋值运算符(copy-and-swap + 按值传参) ======
    // 同时处理拷贝和移动!按值传参:左值→拷贝,右值→移动
    /*
    DynamicArray& operator=(DynamicArray other) noexcept {
        swap(other);
        return *this;
    }
    */

    void swap(DynamicArray& other) noexcept {
        std::swap(data_, other.data_);
        std::swap(size_, other.size_);
        std::swap(capacity_, other.capacity_);
    }

    void push_back(const T& value) {
        if (size_ >= capacity_) {
            size_t new_cap = capacity_ == 0 ? 1 : capacity_ * 2;
            T* new_data = new T[new_cap];
            std::copy(data_, data_ + size_, new_data);
            delete[] data_;
            data_ = new_data;
            capacity_ = new_cap;
        }
        data_[size_++] = value;
    }

    size_t size() const { return size_; }

    T& operator[](size_t index) { return data_[index]; }
    const T& operator[](size_t index) const { return data_[index]; }

    void print() const {
        std::cout << "[";
        for (size_t i = 0; i < size_; ++i) {
            if (i > 0) std::cout << ", ";
            std::cout << data_[i];
        }
        std::cout << "] (size=" << size_ << ")" << std::endl;
    }

private:
    T* data_;
    size_t size_;
    size_t capacity_;
};

int main() {
    std::cout << "===== 构造 =====" << std::endl;
    DynamicArray<int> arr1;
    arr1.push_back(1);
    arr1.push_back(2);
    arr1.push_back(3);
    std::cout << "arr1: ";
    arr1.print();

    std::cout << "\n===== 拷贝赋值 =====" << std::endl;
    DynamicArray<int> arr2;
    arr2 = arr1;                      // 拷贝赋值
    std::cout << "arr2: ";
    arr2.print();

    std::cout << "\n===== 移动赋值 =====" << std::endl;
    DynamicArray<int> arr3;
    arr3 = std::move(arr1);           // 移动赋值
    std::cout << "arr3: ";
    arr3.print();
    std::cout << "arr1 (移动后): ";
    arr1.print();                     // 应为空

    std::cout << "\n===== 自赋值安全 =====" << std::endl;
    arr2 = arr2;                      // 自我赋值——copy-and-swap 天然安全
    std::cout << "arr2: ";
    arr2.print();

    return 0;
}

预期输出:

===== 构造 =====
arr1: [1, 2, 3] (size=3)

===== 拷贝赋值 =====
  [拷贝构造] size=3
  [拷贝赋值]
  [析构]
arr2: [1, 2, 3] (size=3)

===== 移动赋值 =====
  [移动赋值]
arr3: [1, 2, 3] (size=3)
arr1 (移动后): [] (size=0)

===== 自赋值安全 =====
  [拷贝构造] size=3
  [拷贝赋值]
  [析构]
arr2: [1, 2, 3] (size=3)

逐段分析:

  • 拷贝赋值(copy-and-swap):创建 other 的副本 temp → 与 *this 交换 → temp 析构带走旧资源。即使拷贝构造抛异常,*this 仍保持原状态——强异常安全
  • 移动赋值:先释放自己的旧资源,再接管对方的资源。移动操作标记 noexcept,让 STL 容器放心使用
  • copy-and-swap 对自赋值天然安全:temp(other) 创建独立的副本,swap 自己和自己也没问题
  • 注释中的统一赋值运算符:operator=(DynamicArray other) 按值传参,左值时自动拷贝、右值时自动移动——一个函数处理两种赋值。但略有额外的移动构造开销

示例二:银行账户的赋值操作

#include <iostream>
#include <string>

class BankAccount {
public:
    BankAccount(std::string owner, double balance)
        : owner_(std::move(owner)), balance_(balance) {
        std::cout << "  创建账户: " << owner_ << " (¥" << balance_ << ")" << std::endl;
    }

    // 拷贝构造
    BankAccount(const BankAccount& other)
        : owner_(other.owner_ + " (副本)")   // 账户名不能重复
        , balance_(0.0) {                     // 余额不复制
        std::cout << "  [拷贝] 创建副本账户: " << owner_ << std::endl;
    }

    // 移动构造
    BankAccount(BankAccount&& other) noexcept
        : owner_(std::move(other.owner_))
        , balance_(other.balance_) {
        other.balance_ = -1;  // 标记为无效
        std::cout << "  [移动] 所有权转移: " << owner_ << std::endl;
    }

    // 拷贝赋值
    BankAccount& operator=(const BankAccount& other) {
        if (this != &other) {
            std::cout << "  拷贝账户信息(但余额不复制)——" << other.owner_ << std::endl;
            owner_ = other.owner_ + " (副本)";
            // balance_ 保持不变——每个账户的余额独立
        }
        return *this;
    }

    // 移动赋值
    BankAccount& operator=(BankAccount&& other) noexcept {
        if (this != &other) {
            std::cout << "  转移所有权: " << other.owner_ << " → " << owner_ << std::endl;
            owner_ = std::move(other.owner_);
            balance_ = other.balance_;
            other.balance_ = -1;
        }
        return *this;
    }

    void deposit(double amount) { balance_ += amount; }
    void print() const {
        std::cout << "  " << owner_ << ": ¥" << balance_ << std::endl;
    }

private:
    std::string owner_;
    double balance_;
};

int main() {
    std::cout << "===== 银行账户 =====" << std::endl;

    BankAccount alice("张三", 1000.0);
    BankAccount bob("李四", 500.0);

    std::cout << "\n--- 拷贝赋值 ---" << std::endl;
    alice = bob;
    alice.deposit(200.0);  // alice 的余额独立于 bob
    alice.print();
    bob.print();

    std::cout << "\n--- 移动赋值 ---" << std::endl;
    BankAccount temp("临时账户", 999.0);
    bob = std::move(temp);
    bob.print();
    temp.print();  // 余额为 -1(已转移)

    std::cout << "\n--- 自赋值检查 ---" << std::endl;
    alice = alice;  // 安全
    alice.print();

    return 0;
}

预期输出:

===== 银行账户 =====
  创建账户: 张三 (¥1000)
  创建账户: 李四 (¥500)

--- 拷贝赋值 ---
  拷贝账户信息(但余额不复制)——李四
  张三 (副本): ¥1000
  李四: ¥500

--- 移动赋值 ---
  创建账户: 临时账户 (¥999)
  转移所有权: 临时账户 → 李四
  临时账户: ¥999
  临时账户: ¥-1

--- 自赋值检查 ---
  张三 (副本): ¥1000

易错场景与面试考点

易错场景

场景错误表现正确做法
赋值未处理自赋值释放后使用已释放内存if (this != &rhs) 或用 copy-and-swap
移动赋值未释放旧资源旧资源泄漏先 delete[] 再接管
移动赋值后源对象未重置悬垂指针将源对象置空(data=nullptr 等)
移动赋值不是 noexceptSTL 容器回退到拷贝移动赋值必须标记 noexcept
copy-and-swap 中 swap 抛异常违背强异常保证swap 必须 noexcept

常见面试问题

  1. copy-and-swap 惯用法是什么?有什么优点?——赋值运算符中:创建参数副本 → swap 交换 → 副本析构。优点:自赋值安全、强异常安全、代码简洁。

  2. 移动赋值和拷贝赋值的区别?——拷贝赋值创建参数副本并交换;移动赋值直接接管参数资源(不拷贝),源对象置空。移动赋值必须 noexcept。

  3. 传统赋值实现和 copy-and-swap 的区别?——传统写法需手动检查自赋值、先分配再释放(异常时可能泄漏)。copy-and-swap 通过 swap 一次性替换,天然安全。

  4. 统一赋值运算符 operator=(T other) 的原理?——参数按值传递,左值参数触发拷贝构造,右值参数触发移动构造。函数体内只需 swap。代价是多一次移动构造/拷贝构造。

  5. 为什么移动赋值/移动构造要标记 noexcept?——STL 容器在扩容时,如果移动操作不是 noexcept,会回退到拷贝操作以保证异常安全。标记 noexcept 让容器放心使用移动语义。

小结

  • copy-and-swap 是拷贝赋值的最佳实践:拷贝参数 → swap → 析构旧值
  • 移动赋值先释放旧资源,再接管新资源,源对象置空
  • 自赋值检查:传统写法手动检查,copy-and-swap 天然安全
  • 移动赋值和 swap 必须 noexcept——保证 STL 容器正常使用移动优化
  • 默认使用编译器生成的赋值运算符(Rule of Zero)——除非管理裸资源
上一页
算术与关系运算符重载
下一页
特殊运算符重载