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))
相关推荐
2501_944521594 小时前
Ramda 跨端三方库仓颉适配全流程
开发语言·javascript·flutter·php
行思理5 小时前
go语言学习难度及适合学习的人群
开发语言·学习·golang
青瓦梦滋5 小时前
WebSocket协议
linux·服务器·网络·c++·websocket·网络协议·http
wangchunyu1146 小时前
JAVA CompletableFuture异步编程实战
java·开发语言·python
程序 代码狂人7 小时前
切片表与分区表的区别(数据开发面试版)
java·大数据·开发语言·数据仓库·算法
meiying88389 小时前
快递单号批量查询Python脚本:从手动到自动化的完整实现
开发语言·python·自动化
@大迁世界9 小时前
告别 Go,拥抱 Rust
开发语言·golang·rust
SEO_juper9 小时前
Node.js 重定向链检测工具:301 跳了几跳、有没有成环,一次看清
xml·开发语言·python·node.js·json·bash
学逆向的9 小时前
C 指针常量与常量指针
c语言·开发语言·网络安全
低调滴开发10 小时前
3 行代码写崩一个函数?切片才是 Rust 字符串处理的正确姿势
开发语言·后端·rust