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)) 基础类型 intdouble
函数参数按值传入,内部移动到成员 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)
相关推荐
matlab代码1 小时前
基于matlab的玉米种子计数设计(GUI界面)【源码58期】
开发语言·matlab
c#上位机1 小时前
C#上位机项目实战——C#的dll项目编译时拷贝到指定目录下
开发语言·c#
Littlehero_1211 小时前
QT自定义控件之内嵌报表(4)(源码开源)
开发语言·qt
晓晓_za8986681 小时前
开源 GEO 优化源码二次开发:贴牌改造与业务模块扩展实践
java·运维·服务器·开发语言·性能优化·开源
曹牧2 小时前
C#:数组与列表的差异
开发语言·c#
LINgZone22 小时前
后端Java编码规范
java·开发语言
阿里嘎多学长2 小时前
2026-08-24 GitHub 热点项目精选
开发语言·程序员·github·代码托管
SLD_Allen3 小时前
Go语言runtime全景
开发语言·javascript·golang