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

    • 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++ 专业术语

移动语义性能对比与最佳实践

定义与作用

移动语义的核心价值在于性能优化——将深拷贝(O(n))降为指针交换(O(1))。本章通过实测数据展示移动语义的实际收益,并分析 noexcept 对标准容器行为的关键影响。

// 拷贝:O(n) 深拷贝
std::vector<int> v2 = v1;        // 复制所有元素

// 移动:O(1) 指针交换
std::vector<int> v2 = std::move(v1);  // 仅交换内部指针

核心原理

vector 扩容时的 noexcept 决策

为什么标准库关心 noexcept:vector 扩容时,如果移动构造可能抛异常,已移动的元素无法恢复原状(源对象已损坏),破坏强异常安全保证。因此标准库宁可回退到拷贝。

完整示例

示例一:大对象移动 vs 拷贝性能对比

场景说明:大翔让白歌做性能基准测试,对比飞翔科技日志系统中大块数据在拷贝和移动下的耗时差异。

#include <iostream>
#include <chrono>
#include <vector>
#include <cstring>

class DataBlock {
public:
    explicit DataBlock(size_t bytes)
        : size_(bytes), data_(new char[bytes]) {
        std::memset(data_, 'X', bytes);
    }

    // 拷贝(昂贵)
    DataBlock(const DataBlock& other)
        : size_(other.size_), data_(new char[other.size_]) {
        std::memcpy(data_, other.data_, size_);
    }

    // 移动(廉价,noexcept 关键!)
    DataBlock(DataBlock&& other) noexcept
        : size_(other.size_), data_(other.data_) {
        other.data_ = nullptr;
        other.size_ = 0;
    }

    ~DataBlock() { delete[] data_; }

    size_t size() const { return size_; }

private:
    size_t size_;
    char* data_;
};

// 计时工具
class Timer {
    using Clock = std::chrono::high_resolution_clock;
    Clock::time_point start_ = Clock::now();
public:
    double elapsed_ms() const {
        return std::chrono::duration<double, std::milli>(
            Clock::now() - start_).count();
    }
};

int main() {
    const size_t BLOCK_SIZE = 10 * 1024 * 1024;  // 10 MB
    const size_t COUNT = 100;

    // 创建源数据
    DataBlock source(BLOCK_SIZE);

    // 测试1:拷贝性能
    {
        Timer t;
        std::vector<DataBlock> copies;
        copies.reserve(COUNT);
        for (size_t i = 0; i < COUNT; ++i) {
            copies.push_back(source);  // 拷贝 10MB
        }
        std::cout << "[拷贝] " << COUNT << " 个 " << BLOCK_SIZE / 1024 / 1024
                  << "MB 块: " << t.elapsed_ms() << " ms\n";
    }

    // 测试2:移动性能(需要每次重新创建源)
    {
        Timer t;
        std::vector<DataBlock> moved;
        moved.reserve(COUNT);
        for (size_t i = 0; i < COUNT; ++i) {
            DataBlock tmp(BLOCK_SIZE);
            moved.push_back(std::move(tmp));  // 移动,O(1)
        }
        std::cout << "[移动] " << COUNT << " 个 " << BLOCK_SIZE / 1024 / 1024
                  << "MB 块: " << t.elapsed_ms() << " ms\n";
    }
}

预期输出(典型结果):

[拷贝] 100 个 10MB 块: 1250.3 ms
[移动] 100 个 10MB 块: 2.1 ms

逐段分析:

  • 拷贝:每次 push_back 都要 memcpy 10MB 数据 → 总拷贝 1GB → 约 1.25 秒
  • 移动:每次仅交换一个指针(8 字节)→ 总操作 800 字节 → 约 2 毫秒
  • 性能差距约 600 倍,数据越大差距越悬殊
  • noexcept 标记让 vector 扩容时也可以安全使用移动

示例二:noexcept 对 vector 扩容的影响

场景说明:小崔在代码审查中发现一个自定义类缺少 noexcept 移动构造,导致 vector 扩容性能退化为拷贝。

#include <iostream>
#include <vector>
#include <string>

class ConfigEntry {
public:
    ConfigEntry(std::string key, std::string value)
        : key_(std::move(key)), value_(std::move(value)) {
        std::cout << "[构造] " << key_ << "\n";
    }

    // 移动构造 —— 注意:没有 noexcept
    ConfigEntry(ConfigEntry&& other)
        : key_(std::move(other.key_)), value_(std::move(other.value_)) {
        // std::cout << "[移动] " << key_ << "\n";
    }

    // 拷贝构造
    ConfigEntry(const ConfigEntry& other)
        : key_(other.key_), value_(other.value_) {
        std::cout << "[拷贝] " << key_ << "\n";
    }

private:
    std::string key_;
    std::string value_;
};

// 修复版本:加上 noexcept
class ConfigEntrySafe {
public:
    ConfigEntrySafe(std::string key, std::string value)
        : key_(std::move(key)), value_(std::move(value)) {}

    ConfigEntrySafe(ConfigEntrySafe&& other) noexcept  // noexcept!
        : key_(std::move(other.key_)), value_(std::move(other.value_)) {}

    ConfigEntrySafe(const ConfigEntrySafe& other)
        : key_(other.key_), value_(other.value_) {
        std::cout << "[拷贝] " << key_ << "\n";
    }

private:
    std::string key_;
    std::string value_;
};

int main() {
    std::cout << "=== 无 noexcept:vector 扩容回退拷贝 ===\n";
    std::vector<ConfigEntry> vec1;
    vec1.push_back(ConfigEntry("飞翔科技-孔蓝-需求A", "v1.0"));
    vec1.push_back(ConfigEntry("飞翔科技-孔蓝-需求B", "v1.0"));
    vec1.push_back(ConfigEntry("飞翔科技-孔蓝-需求C", "v1.0"));
    // 扩容时:移动构造无 noexcept → 回退拷贝

    std::cout << "\n=== 有 noexcept:vector 扩容使用移动 ===\n";
    std::vector<ConfigEntrySafe> vec2;
    vec2.reserve(3);
    vec2.push_back(ConfigEntrySafe("飞翔科技-白歌-架构A", "v1.0"));
    vec2.push_back(ConfigEntrySafe("飞翔科技-白歌-架构B", "v1.0"));
    vec2.push_back(ConfigEntrySafe("飞翔科技-白歌-架构C", "v1.0"));
    // 扩容时:移动构造有 noexcept → 使用移动
}

预期输出:

=== 无 noexcept:vector 扩容回退拷贝 ===
[构造] 飞翔科技-孔蓝-需求A
[构造] 飞翔科技-孔蓝-需求B
[拷贝] 飞翔科技-孔蓝-需求A
[构造] 飞翔科技-孔蓝-需求C
[拷贝] 飞翔科技-孔蓝-需求A
[拷贝] 飞翔科技-孔蓝-需求B

=== 有 noexcept:vector 扩容使用移动 ===
(无拷贝输出)

逐段分析:

  • vec1:扩容时 ConfigEntry 移动构造没有 noexcept → vector 保守地使用拷贝构造维护强异常安全
  • vec2:ConfigEntrySafe 标记了 noexcept → vector 放心使用 O(1) 移动
  • 实战启示:所有移动构造和移动赋值都应标记 noexcept

易错场景与面试考点

易错场景

1. 忘记 noexcept 导致性能退化

class Widget {
    Widget(Widget&& w);  // 无 noexcept → 标准容器不会使用!
};

2. 对 const 对象使用 move

const std::vector<int> v = {1, 2, 3};
auto v2 = std::move(v);  // 调用拷贝构造!const T&& 匹配 const T&

3. 移动后继续使用

auto data = std::make_unique<int[]>(1024);
auto moved = std::move(data);
data[0] = 42;  // 未定义行为!data 已为空

面试考点

考点要点
移动 vs 拷贝性能O(1) 指针交换 vs O(n) 深拷贝
noexcept 对容器的影响决定 vector 扩容时使用移动还是拷贝
Rule of Five自定义五者之一通常应全部显式定义
移动后对象状态有效但未指定,必须可安全析构和重新赋值
= default 移动编译器自动生成 noexcept 移动(条件满足时)
move-only 类型unique_ptr、thread、future、文件句柄
上一页
完美转发与 std::forward