现代C++学习:右值引用|移动语义|完美转发

一、 什么是左值右值

在 C++ 中,值可以根据不同的标准进行分类。按照 表达式求值结果的类别,通常分为以下 三种值类别(Value Categories)

1. 左值(Lvalue, Locator Value)

  • 特点:表示 可以被取地址(可绑定到左值引用 T&)。
  • 常见情况
    • 变量名:int x = 10;,其中 x 是左值。
    • 返回左值引用的函数:int& foo();foo() 是左值。
    • 解引用指针:*ptr 是左值。

2. 亡值(Xvalue, eXpiring Value)

  • 特点即将被销毁的对象,可绑定到 T&& 右值引用,但仍具有 可取地址性(像左值)。
  • 常见情况
    • 返回右值引用的函数std::move(x) 产生亡值。
    • 临时对象的成员std::move(x).member

3. 纯右值(Prvalue, Pure Rvalue)

  • 特点不能取地址,表示一个临时值,通常用于初始化和传递。
  • 常见情况
    • 字面量:42, "hello", 3.14
    • 临时对象:std::string("test")
    • 返回非引用的函数:std::string foo();

但是在 C++17 之后,纯右值(Prvalue)不再有实体对象,而是用于初始化时直接构造目标对象
可见左右值的概念很清晰,有地址的变量就是左值,没有地址的字面值、临时值就是右值。

二、 左值引用和右值引用

1. 左值引用

int a = 10;
int &ref1 = a;
int &ref2 = 5; // 编译错误
const int &ref3 = 5; // 编译通过
  • 编译错误是因为,左值引用不能指向右值,但是加上const之后能解决这个问题
  • const T&既能指向左值,也能指向右值,举个例子,std::vector<T>::push_back
void push_back(const T& value); // 不加const,push_back(10)就会编译错误

2. 右值引用

  • 右值引用能指向右值,本质上也是把右值提升为一个左值,并定义一个右值引用通过std::move指向该左值:
  • std::move:让左值强行转换为右值,等同于static_cast<T&&>(lvalue)
int &&ref = 5;
ref++;

等同于

int a = 5;
int &&ref = std::move(a);
ref++;

继续以std::vector<T>为例,观察到源码:

#if __cplusplus >= 201103L
void push_back(value_type&& __x) { emplace_back(std::move(__x)); }
// 说明右值引用本身是左值
  • push_back 接受一个对象(左值或右值),然后 拷贝(或移动) 该对象到容器中。
  • 如果传递的不是 T 类型的对象,需要先构造 T 类型的对象,再拷贝(或移动)到容器中。
  • 这里举例说明一下emplace_back的优势
#include <vector>
#include <iostream>

struct Foo {
    Foo(int x, int y) { std::cout << "Foo(int, int)\n"; }
};

int main() {
    std::vector<Foo> vec;
    Foo f(1, 2);        // 调用 Foo(int, int)
    vec.push_back(f);   // 发生一次拷贝
    vec.push_back(Foo(3, 4)); // 发生一次构造 + 一次移动
	v2.emplace_back(5, 6); // 直接构造,无拷贝/移动,避免了临时对象的创建
}

  • emplace_back可以省略一次构建和一次析构,从而达到优化的目的
  • 但是我们在需要明确拷贝构造的语义的时候,需要使用push_back

三、 实现移动语义

std::string s = "abcabc";
std::vector<std::string> vec2;
vec2.push_back(std::move(s)); // 调用移动语义的push_back方法,避免拷贝,s会失去原有值,变成空字符串
std::cout << s << '\n'; // 输出空,因为所有权被转移

因此,可移动对象在<需要拷贝且被拷贝者之后不再被需要>的场景,建议使用std::move触发移动语义,提升性能。

绝大多数 STL 容器和工具类都正确实现了移动语义

  • 所有标准容器​(如 std::vector, std::string, std::list, std::map 等)均正确实现了移动语义。
  • 智能指针​(std::shared_ptr)通过所有权转移实现移动语义。
  • 工具类​(如 std::thread, std::fstream)也支持移动语义,避免资源重复释放。
  • 还有些STL类是move-only的,比如unique_ptr,这种类只有移动构造函数,因此只能移动(转移内部对象所有权,或者叫浅拷贝),不能拷贝(深拷贝)
  • std::array 是特例,它本质是固定大小的栈上数组,移动语义会逐个元素移动(类似拷贝)。

特例

int a = 10;
std::vector<int> vec;
vec.push_back(std::move(a));
std::cout << a << '\n'; // 输出10,说明所有权并没有被转移
  • 对于内置类型(如int,double,void,nullptr),移动操作与拷贝操作完全等价,内置类型的值直接存储在变量内存中,没有动态分配的资源(如堆内存)。
  • std::move(a)实际上执行的是拷贝操作,原始变量a的值保持不变。
  • 内置类型没有真正的"移动"语义,移动即拷贝

自定义类型如何实现类似 STL 的移动语义?

#include <iostream>
#include <vector>

class Array {
    public:
        int *ptr, siz;
        Array(int siz_) {
            siz = siz_;
            ptr = new int[siz];
        }
        Array(Array &&v) noexcept {
            ptr = v.ptr;
            siz = v.siz;
            v.ptr = nullptr; // 置空指针,防止析构时重复释放内存,实现移动语义
            v.siz = 0;
        }
        ~Array() {
            delete[] ptr; 
        }
};

int main() {
    Array arr(10);
    Array arr2 = std::move(arr);
    return 0;
}

标记为 noexcept:确保容器(如 std::vector)在扩容时优先使用移动而非拷贝。

四、完美转发

完美转发的关键机制

  1. 万能引用(Universal Reference)​
    模板参数声明为 T&&,通过类型推导和引用折叠规则,可接受左值或右值:

    • 传递左值时,T 推导为 T&T&& 折叠为 T&(左值引用)。
    • 传递右值时,T 推导为 TT&& 保持为 T&&(右值引用)。
  2. std::forward
    根据模板参数的类型,有条件地将参数转换为左值或右值引用:

    • 若原始参数为左值,std::forward 返回左值引用。
    • 若原始参数为右值,std::forward 返回右值引用。

举个例子

void target(int&)  { std::cout << "左值引用" << std::endl; }
void target(int&&) { std::cout << "右值引用" << std::endl; }

// 中间层函数(无完美转发)
template <typename Arg>
void bad_forward(Arg &&arg) {
    target(arg); // arg 始终是左值,因为无论是左值引用还是右值引用都是左值
}

// 中间层函数(使用完美转发)
template <typename Arg>
void good_forward(Arg&& arg) {
    target(std::forward<Arg>(arg));
}

int main() {
    int x = 5;
    bad_forward(x);   // 输出 "左值引用"
    bad_forward(10);  // 输出 "左值引用"(错误!期望触发右值引用)

    good_forward(x);  // 输出 "左值引用"
    good_forward(10); // 输出 "右值引用"(正确!)
}

想要完全了解完美转发的机制,仍需深入学习万能引用引用折叠等知识点,此处不作展开

如有错误,请指正!!!

关注主包谢谢喵~~~,分享更多有用的知识

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值