找回密码
 立即注册
首页 业界区 业界 【C++】移动语义和完美转发

【C++】移动语义和完美转发

喳谍 2026-1-13 21:35:28
前言

学习C++移动语义和完美转发笔记,记录左值、右值、std::move()、万能引用、引用折叠等相关内容。
概念


  • 左值 (lvalue) 它是在内存中有明确存储地址、可以被寻址的值。如果你可以对一个表达式取地址(使用 & 运算符),那么它就是一个左值。左值通常是持久的,在它所在的定义域结束之前一直存在
  • 左值引用(Lvalue Reference)本质上就是给一个现有的左值起了一个“别名”,左值引用定义即初始化。

    • 普通左值引用 (T&):只能绑定到非 const 左值
    • 常量左值引用 (const T&):可以绑定到一切(左值、const 左值、右值)。

  1. const int& temp = 10;
  2. //编译器会在内存中产生一个临时变量存储 10。
  3. //temp 绑定到这个临时变量上。
  4. //这个临时变量的寿命会变得和引用 temp 一样长。
复制代码

  • 右值 (rvalue) 右值就是那些临时出现、没有持久名字、无法取地址的值。如果你无法对一个表达式使用 & 取地址运算符,或者它是一个即将销毁的临时对象,它就是右值。右值通常是“瞬时”的,在包含它的表达式执行完之后,它就会被立即销毁.
  • 右值引用(Rvalue Reference)一种绑定到右值(临时对象)的引用类型
    int&& rref = 10;

  • std::move 并不移动任何东西。它的唯一作用是:强制将一个左值转为右值引用
    1. template <typename T>
    2. typename std::remove_reference<T>::type&& move(T&& t) {   
    3.     return static_cast<typename std::remove_reference<T>::type&&>(t);
    4. }
    复制代码

    • std::remove_reference::type:这是一个类型萃取工具。无论 T 是 int、int& 还是 int&&,它都能把修饰符去掉,只留下纯粹的底层类型 int。
    • static_cast 将输入变量 t 强制转换为该类型的右值引用(即 type&&)

  • std::forward 如果原始参数是左值,转发后仍然是左值。如果原始参数是右值,转发后仍然是右值
  1. template <typename T>
  2. T&& forward(typename std::remove_reference<T>::type& t) noexcept {
  3.     return static_cast<T&&>(t);
  4. }
复制代码

  • 移动语义(Move Semantics) 本质是资源所有权的转移,即将一个临时对象(右值)持有的资源转移,来避免昂贵的深拷贝操作。是由类实现的功能(通过移动构造函数)
  • 完美转发(Perfect Forwarding) 是:在函数模板中,将参数原封不动地转发给另一个函数,同时完全保留参数的所有属性(包括它是左值还是右值、是否带有 const 或 volatile 修饰符)。
    1. template <typename T>
    2. T&& forward(typename std::remove_reference<T>::type& t) noexcept {
    3.     return static_cast<T&&>(t);
    4. }
    复制代码

    • 如果原始参数是左值,转发后仍然是左值
    • 如果原始参数是右值,转发后仍然是右值

  • 万能引用

    • 使用 && 符号
    • 必须发生模板类型推导:通常出现在 template  之后的 T&&(不能加 const)。
    • 形式必须完全匹配:必须是具体的 T&&,不能有 const 或 std::vector&& 等修饰。
    • 万能引用是完美转发的“门”。 它把参数原封不动地领进来(无论是左值还是右值),然后配合 std::forward 把它原封不动地送出去。

  • 引用折叠  是 C++ 编译器在处理“引用的引用”时遵循的一套自动简化规则。只要有左值引用(&)参与,结果就是左值引用;只有全是右值引用(&&)时,结果才是右值引用。
内容

左值

内存中有明确存储地址、可以被寻址的值
  1. int a = 10;        // a 是左值(有名字,可取地址)
  2. a = 20;            // a 在左边,OK
  3. int* p = &a;       // p 是左值
  4. *p = 30;           // *p(解引用结果)是左值
  5. int** pp = &(*p);  // 我们可以对 (*p) 再次取地址 ,可以看到*p是可以取地址的
  6. const int b = 5;   // b 是左值(虽然不可修改,但它有内存地址,是具名变量)
  7. void func(int&& x) {
  8.     // 这里的 x 类型是右值引用,但 x 本身是一个有名字的变量
  9.     // 所以在函数内部,x 是一个左值!
  10.     int* p = &x; // 这是合法的
  11. }
复制代码
左值引用

对左值的引用,即给左值起别名,必须初始化。
[code]int a = 10;int& ref = a;  // ref 是 a 的左值引用const int& r3 = 10; // 正确!std::cout

相关推荐

2026-1-24 09:34:47

举报

昨天 03:03

举报

懂技术并乐意极积无私分享的人越来越少。珍惜
您需要登录后才可以回帖 登录 | 立即注册