C++左值右值的使用场景


一、强烈推荐使用右值引用的场景

1. 实现移动构造函数和移动赋值运算符(核心场景)

class MyBuffer {
public:
    // ✅ 移动构造函数
    MyBuffer(MyBuffer&& other) noexcept
        : data_(other.data_), size_(other.size_) {
        other.data_ = nullptr;   // 把源对象置空
        other.size_ = 0;
    }

    // ✅ 移动赋值运算符
    MyBuffer& operator=(MyBuffer&& other) noexcept {
        if (this != &other) {
            delete[] data_;      // 释放自己的旧资源
            data_ = other.data_;
            size_ = other.size_;
            other.data_ = nullptr;
            other.size_ = 0;
        }
        return *this;
    }

private:
    char* data_;
    size_t size_;
};

为什么推荐:这是移动语义存在的根本原因,能显著提升性能。

上面的还有简化实现方式:

class MyBuffer {
public:
    // 移动赋值运算符(极简版)
    MyBuffer& operator=(MyBuffer&& other) noexcept {
        if (this != &other) {
            // 借用 swap,利用移动构造的临时对象
            MyBuffer temp(std::move(other));
            swap(temp);
        }
        return *this;
    }

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

2. 实现完美转发(万能引用 + std::forward

// ✅ 万能引用 + std::forward 实现完美转发
template <typename T>
void wrapper(T&& arg) {                    // T&& 是万能引用
    target(std::forward<T>(arg));          // 保持左值/右值属性
}

// 应用场景:工厂函数、代理模式、装饰器模式
template <typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

为什么推荐:让参数以最合适的方式(拷贝或移动)传递,避免不必要的拷贝。


3. 重载函数以优化右值参数

class Container {
    std::vector<int> data_;
public:
    // ✅ 接收左值:拷贝
    void push_back(const int& value) {
        data_.push_back(value);
    }

    // ✅ 接收右值:移动(避免拷贝)
    void push_back(int&& value) {
        data_.push_back(std::move(value));
    }
};

// 使用
Container c;
int x = 42;
c.push_back(x);           // 调用左值版本(拷贝)
c.push_back(100);         // 调用右值版本(移动)
c.push_back(std::move(x)); // 调用右值版本(移动)

为什么推荐:给调用者提供选择,用移动优化临时对象和 std::move 的场景。


4. 移动容器元素(转移所有权)

std::vector<std::string> strings;
std::string s = "hello";

// ✅ 移动插入,避免拷贝
strings.push_back(std::move(s));  // s 被移走,变为空

// 或者从临时对象插入
strings.push_back("world"s);      // 右值,自动移动

为什么推荐:容器操作是性能热点,移动比拷贝快得多。


5. 转移独占资源(unique_ptr 等)

std::unique_ptr<MyClass> createObject() {
    auto ptr = std::make_unique<MyClass>();
    // ... 初始化
    return ptr;  // ✅ 自动移动(或 NRVO)
}

// 转移所有权
std::unique_ptr<MyClass> obj1 = createObject();
std::unique_ptr<MyClass> obj2 = std::move(obj1);  // ✅ obj1 变为空

为什么推荐std::unique_ptr 只能移动不能拷贝,右值引用是实现它的基础。


6. 自定义移动迭代器

std::vector<std::string> source = {"a", "b", "c"};
std::vector<std::string> dest;

// ✅ 移动迭代器:把 source 的元素移动到 dest
std::move(source.begin(), source.end(), std::back_inserter(dest));
// source 中的元素被移走,变为空字符串

为什么推荐:批量移动比循环移动更高效。


二、不推荐使用右值引用的场景

❌ 1. 对基本类型使用右值引用

// ❌ 完全没有意义
void process(int&& x) { }

int a = 42;
process(a);            // ❌ 编译错误(左值)
process(std::move(a)); // ✅ 能编译,但毫无意义

// 基本类型拷贝和移动成本相同,没必要用右值引用

更好的写法:直接用值传递或 const 引用。


❌ 2. 在函数返回值中使用 std::move

// ❌ 错误用法(阻止 NRVO)
MyClass createObject() {
    MyClass obj;
    return std::move(obj);  // 阻止返回值优化(RVO/NRVO)
}

// ✅ 正确写法
MyClass createObject() {
    MyClass obj;
    return obj;  // 编译器自动应用移动(或 NRVO)
}

原因:C++17 保证复制省略(Copy Elision),return obj 是最高效的写法。


❌ 3. 移动后继续使用对象

std::string s = "hello";
std::string t = std::move(s);

// ❌ 危险!s 被移走后处于"有效但未指定"状态
std::cout << s;      // 可能崩溃或输出空字符串
s.size();            // 可能返回 0,也可能未定义行为

// ✅ 正确做法:移动后不再使用,除非重新赋值
s = "new value";     // 重新赋值后可以安全使用

原因:被移动的对象处于"有效但未指定"状态,访问它是不安全的。


❌ 4. 在 const 对象上使用 std::move

const std::string s = "hello";
auto t = std::move(s);  // ❌ 编译失败或退化为拷贝

// std::move(const T&) 返回 const T&&
// const T&& 只能绑定到 const T&& 参数,通常不存在这样的重载
// 最终会退化为拷贝构造

原因:不能从 const 对象移动,因为移动需要修改原对象。


❌ 5. 对生命周期很短的局部变量

void process(std::string&& s) { }

void example() {
    // ❌ 过度设计
    std::string s = "hello";
    process(std::move(s));  // 因为 process 是右值版本
    // 之后 s 被移走,无法再用

    // ✅ 简单写法
    process("hello"s);      // 直接传临时对象,自动移动
}

原因:临时对象本身就是右值,不需要额外的 std::move


三、快速决策表

场景 是否推荐 说明
移动构造函数/赋值运算符 强烈推荐 移动语义的核心
完美转发(T&& + std::forward 强烈推荐 泛型编程必备
函数重载优化(左值/右值版本) ✅ 推荐 给调用者提供选择
容器插入(push_back(std::move(x)) ✅ 推荐 性能提升明显
unique_ptr 转移所有权 ✅ 推荐 只能靠移动
返回局部对象时 ❌ 不要用 std::move 会阻止 NRVO,用 return obj;
基本类型(intdouble ❌ 不推荐 拷贝和移动成本相同
移动后继续使用 ❌ 危险 未定义行为
const 对象 ❌ 无效 不能从 const 移动
作为函数参数类型 ⚠️ 谨慎 一般用值传递 + std::move 更好

四、一句话总结

使用右值引用的黄金法则:用于"转移资源所有权"(移动构造、完美转发、容器优化),不要用于"传递基本类型"或"返回局部对象时加 std::move"。移动后,把源对象当作"空壳",不再访问。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容