C++ std::move 详解

C++ std::move 详解

1. 核心概念

std::move 实现的是移动语义(move semantics),用于转移资源所有权,避免昂贵的深拷贝。

关键认知:std::move 本身不移动任何东西 ,它只是一个 static_cast,把左值无条件转成右值引用(T&&),真正的"移动"发生在移动构造/移动赋值函数里。

cpp 复制代码
// 等价定义
template<typename T>
typename std::remove_reference<T>::type&& move(T &&t) noexcept {
    return static_cast<typename std::remove_reference<T>::type&&>(t);
}

2. 基本用法

cpp 复制代码
QString src = "Hello World";
QString dst = std::move(src);   // 调用 QString 的移动构造函数

// src 现在处于"有效但未指定"状态,可析构/可赋值,但不保证内容
qDebug() << src;   // 可能是空,可能是原值,不确定

3. 移动构造 / 移动赋值

cpp 复制代码
class PressureBuffer {
public:
    PressureBuffer() = default;

    // 移动构造
    PressureBuffer(PressureBuffer &&other) noexcept
        : m_data(std::move(other.m_data))
        , m_size(other.m_size) {
        other.m_size = 0;
    }

    // 移动赋值
    PressureBuffer &operator=(PressureBuffer &&other) noexcept {
        if (this != &other) {
            m_data = std::move(other.m_data);
            m_size = other.m_size;
            other.m_size = 0;
        }
        return *this;
    }

private:
    QVector<double> m_data;
    int m_size = 0;
};

注意 :移动操作必须标记 noexcept,否则 STL 容器会退化为拷贝。

4. STL 容器场景(最常用)

场景 1:把局部容器的内容转移出去

cpp 复制代码
QVector<double> generatePressureData() {
    QVector<double> data;
    data.reserve(64);
    for (int i = 0; i < 64; ++i)
        data.push_back(i * 1.5);

    return data;   // RVO 优先,不需要 std::move
    // return std::move(data);  // 错误!会禁止 RVO,反而变慢
}

场景 2:把临时对象塞进容器

cpp 复制代码
QList<SensorData> list;
SensorData data = loadFromDevice();

list.push_back(std::move(data));   // 移动进容器,避免拷贝大数组
// list.push_back(data);          // 拷贝,慢

场景 3:把容器内元素转移给另一容器

cpp 复制代码
QVector<QString> src = {"A", "B", "C"};
QVector<QString> dst;
dst.reserve(src.size());
for (auto &s : src) {
    dst.push_back(std::move(s));   // 逐个移动
}
// src 仍是 size=3,但元素内容未指定

5. Qt 场景实战

cpp 复制代码
void SensorManager::appendData(SensorData data) {
    // 按值传入,函数内移动进容器(避免一次拷贝)
    m_dataList.push_back(std::move(data));
}

// 调用方
QList<SensorData> batch = readBatch();
m_manager->appendData(std::move(batch));   // 移动整个 list
cpp 复制代码
// QString 移动
QString build = "very long string ...";
m_label->setText(std::move(build));   // QString 有移动构造,省拷贝
cpp 复制代码
// QSharedPointer / std::shared_ptr 移动
auto ptr = std::make_shared<Sensor>();
m_currentSensor = std::move(ptr);   // 引用计数原子操作,移动比拷贝快

6. 常见陷阱

陷阱 1:移动 const 对象 → 退化为拷贝

cpp 复制代码
const QString text = "hello";
QString dst = std::move(text);   // text 是 const,无法移动,触发拷贝!

陷阱 2:移动基础类型毫无意义

cpp 复制代码
int a = 10;
int b = std::move(a);   // 合法但无意义,int 还是值拷贝

陷阱 3:移动后继续使用源对象

cpp 复制代码
QString s = "hello";
QString t = std::move(s);
qDebug() << s.length();   // 未定义行为!s 处于"有效但未指定"状态
s = "world";              // 合法,重新赋值后可正常使用

陷阱 4:对返回值用 std::move(最常见错误)

cpp 复制代码
QVector<double> makeData() {
    QVector<double> v = {...};
    return std::move(v);   // ❌ 禁止 RVO,反而触发移动构造
                          // ✅ 直接 return v; 编译器做 RVO,零拷贝
}

陷阱 5:移动自身

cpp 复制代码
x = std::move(x);   // 自赋值,可能内存错误
// 标准库实现通常会检查 this == &other

7. 推荐与不推荐场景

推荐使用 不推荐使用
把大对象塞进容器 vec.push_back(std::move(x)) 基础类型 int、double
函数参数按值传入,内部移动到成员 m_x = std::move(x) return std::move(local)(禁止 RVO)
转移容器所有权(独占资源) const 对象(退化为拷贝)
替代深拷贝大 QVector / QString / shared_ptr 移动后继续读源对象
智能指针转移 std::unique_ptr 小于 2-3 个指针的小结构体(拷贝更快)

8. 关键原则速记

  1. std::move 不移动,只是把左值"伪装"成右值
  2. 真正的移动逻辑在移动构造/赋值函数里实现
  3. 返回局部变量时 return obj; 不要加 std::move(让 RVO 生效)
  4. 移动操作必须 noexcept(否则 STL 容器退化为拷贝)
  5. 移动后的源对象:可析构、可赋值,不可读
  6. const 对象无法移动 (std::move(const T) 还是拷贝)
  7. 优先用值传参 + 内部 std::move (如 void f(QString s) 内部 m_x = std::move(s))
相关推荐
Shadow(⊙o⊙)1 分钟前
C++11并发支持库
开发语言·c++
一技安身2 分钟前
【java】JDK8 + JDK17 绿色解压共存方案-全程不修改任何系统环境变量
java·开发语言
ShineWinsu13 分钟前
对于Redis:主从复制的解析
linux·数据库·c++·redis·缓存·面试·主从复制
hudd6681 小时前
JavaEE 初阶 - 多线程(初阶)
java·开发语言·java-ee
I Am a robert girl1 小时前
稀有事件估计的迭代去对齐:从源码视角拆解重要性采样新范式
开发语言·python·机器学习·重要性采样·蒙特卡洛方法·稀有事件估计
无忧.芙桃1 小时前
C++ 并查集详解:原理、实现与应用
c语言·c++·算法·leetcode
golang学习记1 小时前
rust开发,选VS Code还是RustRover?
开发语言·后端·rust
打工仔折腾 AI2 小时前
从零写一个CAD 05:以鼠标为中心的滚轮缩放,招法能复用但顺序不能反
开发语言·后端·python·性能优化·计算机外设·swift·ai agent 实战
CarIise2 小时前
Java实训阶段查漏补缺复习笔记1
java·开发语言·笔记
事圆则缓2 小时前
Kotlin 泛型方差实战:out、in、星投影与类型擦除
android·开发语言·kotlin