当前位置: 首页 > news >正文

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

  • 左值 (lvalue)它是在内存中有明确存储地址、可以被寻址的值。如果你可以对一个表达式取地址(使用&运算符),那么它就是一个左值。左值通常是持久的,在它所在的定义域结束之前一直存在

  • 左值引用(Lvalue Reference)本质上就是给一个现有的左值起了一个“别名”,左值引用定义即初始化。

    • 普通左值引用 (T&):只能绑定到非 const 左值
    • 常量左值引用 (const T&):可以绑定到一切(左值、const 左值、右值)。
const int& temp = 10; //编译器会在内存中产生一个临时变量存储 10。 //temp 绑定到这个临时变量上。 //这个临时变量的寿命会变得和引用 temp 一样长。
  • 右值 (rvalue)右值就是那些临时出现、没有持久名字、无法取地址的值。如果你无法对一个表达式使用&取地址运算符,或者它是一个即将销毁的临时对象,它就是右值。右值通常是“瞬时”的,在包含它的表达式执行完之后,它就会被立即销毁.

  • 右值引用(Rvalue Reference)一种绑定到右值(临时对象)的引用类型

    int&& rref = 10;

  • std::move并不移动任何东西。它的唯一作用是:强制将一个左值转为右值引用

    template <typename T> typename std::remove_reference<T>::type&& move(T&& t) { return static_cast<typename std::remove_reference<T>::type&&>(t); }
    • std::remove_reference<T>::type:这是一个类型萃取工具。无论Tintint&还是int&&,它都能把修饰符去掉,只留下纯粹的底层类型int

    • static_cast将输入变量t强制转换为该类型的右值引用(即type&&

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

template <typename T> T&& forward(typename std::remove_reference<T>::type& t) noexcept { return static_cast<T&&>(t); }
  • 移动语义(Move Semantics)本质是资源所有权的转移,即将一个临时对象(右值)持有的资源转移,来避免昂贵的深拷贝操作。是由类实现的功能(通过移动构造函数)

  • 完美转发(Perfect Forwarding)是:在函数模板中,将参数原封不动地转发给另一个函数,同时完全保留参数的所有属性(包括它是左值还是右值、是否带有constvolatile修饰符)。

    template <typename T> T&& forward(typename std::remove_reference<T>::type& t) noexcept { return static_cast<T&&>(t); }
    • 如果原始参数是左值,转发后仍然是左值

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

  • 万能引用

    • 使用&&符号
    • 必须发生模板类型推导:通常出现在template <typename T>之后的T&&(不能加const)。
    • 形式必须完全匹配:必须是具体的T&&,不能有conststd::vector<T>&&等修饰。
    • 万能引用是完美转发的“门”。它把参数原封不动地领进来(无论是左值还是右值),然后配合std::forward把它原封不动地送出去。
  • 引用折叠是 C++ 编译器在处理“引用的引用”时遵循的一套自动简化规则。只要有左值引用(&)参与,结果就是左值引用;只有全是右值引用(&&)时,结果才是右值引用。

内容

左值

内存中有明确存储地址、可以被寻址的值

int a = 10; // a 是左值(有名字,可取地址) a = 20; // a 在左边,OK int* p = &a; // p 是左值 *p = 30; // *p(解引用结果)是左值 int** pp = &(*p); // 我们可以对 (*p) 再次取地址 ,可以看到*p是可以取地址的 const int b = 5; // b 是左值(虽然不可修改,但它有内存地址,是具名变量) void func(int&& x) { // 这里的 x 类型是右值引用,但 x 本身是一个有名字的变量 // 所以在函数内部,x 是一个左值! int* p = &x; // 这是合法的 }

左值引用

对左值的引用,即给左值起别名,必须初始化。

int a = 10; int& ref = a; // ref 是 a 的左值引用 const int& r3 = 10; // 正确! std::cout << "a" << "b" << "c"; // 每次调用 << 都返回 cout 的左值引用

右值

没有固定地址、没有名字的值,通常是临时结果、字面量或即将销毁的对象。

10; // 纯右值:字面量 a + b; // 纯右值:运算的中间结果,没有名字 func(5); // 如果 func 返回一个值(非引用),func(5) 就是右值 std::string("Hi"); // 纯右值:临时构造的匿名对象

右值引用

绑定到右值上。 它的符号是&&

int&& r1 = 10; // 正确:10 是右值 int a = 10; int&& r3 = std::move(a); // 正确:std::move 把左值转成了右值(将亡值) int n = 5; // int&& r = ++n; // 错误:++n 返回的是修改后的 n 本身(有地址) int&& r = n++; // 正确:n++ 返回的是一个临时副本(旧值 5),n 本身已变

万能引用&引用折叠

template<typename T> void func(T&& param); // 这是一个万能引用 //场景 A:传入左值变量 int a = 10; func(a); //因为 a 是左值,根据万能引用的特殊推导规则,编译器将 T 推导为 int&。 //为什么不能是int;因为如果为 int,函数变为 void func(int && param) 这个函数只能接收右值,所以编译器只能推导为int & //代入模板:函数签名变成 void func(int& && param);。 //引用折叠:根据规则,int& && 含有左值引用,折叠为 int&。 //最终形态:void func(int& param); —— 成功以左值引用的方式接收了变量。 //场景 B:传入右值字面量 20 func(20); //推导:因为 20 是右值,编译器将 T 推导为 int。 //模板:函数签名变成 void func(int&& param);。 //折叠:没有冲突,或者看作 int&&,保持 int&&。 //最终形态:void func(int&& param); —— 成功以右值引用的方式接收了临时变量

移动语义

我们将一个临时对象赋值给另一个对象时,会触发深拷贝。比如一个包含 很多数据的std::vector,拷贝它需要重新分配内存再复制数据,这非常耗时。

移动语义允许我们直接获取临时对象的资源,只需修改指针指向,而不必重新分配内存。

#include <iostream> #include <vector> #include <utility> class MyBuffer { private: int* data; // 唯一的私有属性 public: // 构造函数 explicit 防止隐式转换 //MyBuffer b2 = 100; ❌ 编译错误,不能隐式转换 explicit MyBuffer(int value) : data(new int(value)) { std::cout << "分配内存并存入: " << *data << std::endl; } // 析构函数 ~MyBuffer() { if (data) { delete data; data = nullptr; std::cout << "释放内存" << std::endl; } } // --------------------------------------------------------- // 拷贝构造函数 (Copy Constructor) - 深拷贝 // --------------------------------------------------------- MyBuffer(const MyBuffer& other) : data(other.data ? new int(*other.data) : nullptr) { std::cout << "深拷贝数据: " << (data ? *data : 0) << std::endl; } // --------------------------------------------------------- // 移动构造函数 (Move Constructor) // --------------------------------------------------------- MyBuffer(MyBuffer&& other) noexcept : data(other.data) { other.data = nullptr; std::cout << "资源所有权已转移" << std::endl; } // --------------------------------------------------------- // 移动赋值运算符 (Move Assignment Operator) // --------------------------------------------------------- MyBuffer& operator=(MyBuffer&& other) noexcept { std::cout << "执行移动赋值" << std::endl; if (this != &other) { delete data; // 1. 释放当前对象持有的旧内存 data = other.data; // 2. 接管新资源 other.data = nullptr; } return *this; } // 访问器方法 int getValue() const { return data ? *data : 0; } void setValue(int value) { if (data) { *data = value; } else { data = new int(value); } } // 检查是否拥有资源 bool isEmpty() const { return data == nullptr; } }; int main() { std::cout << "=== 测试移动语义 ===" << std::endl; // 测试移动构造函数 MyBuffer b1(100); MyBuffer b2(std::move(b1)); // 触发移动构造 // b1 现在是"有效但未指定状态" std::cout << "b1是否为空: " << b1.isEmpty() << std::endl; std::cout << "b2的值: " << b2.getValue() << std::endl; // 测试移动赋值 MyBuffer b3(300); MyBuffer b4(400); std::cout << "\n移动赋值前 - b3: " << b3.getValue() << ", b4: " << b4.getValue() << std::endl; b4 = std::move(b3); std::cout << "移动赋值后 - b3是否为空: " << b3.isEmpty() << std::endl; std::cout << "移动赋值后 - b4: " << b4.getValue() << std::endl; return 0; }
http://www.jsqmd.com/news/1100366/

相关文章:

  • Selenium Web自动化测试:从核心原理到企业级框架实战
  • Kali Linux下从零构建远程控制程序:理解C/S架构与安全攻防原理
  • N_m3u8DL-RE技术深度解析:现代流媒体下载架构实现
  • 3分钟快速上手:终极免费暗黑2存档编辑器的完整指南
  • 冷轧薄板用校平机:为什么这类材料对矫平精度要求最高?
  • 【AWS】基于Docker搭建监控系统基础(二)
  • 手把手教你用QRC提取RC寄生参数:从.cmd文件配置到SPEF输出的完整避坑指南
  • TEA系列加密算法实战:从C到Python的跨平台轻量级实现
  • 2026年,AI搜索优化的技术底层:从向量检索到商品卡交易闭环,每一层到底在做什么
  • 别再踩坑了!用Python控制Agilent 34401A万用表,这个SYSTEM:REMOTE命令必须发
  • ESP32驱动S90舵机保姆级教程:从PWM原理到库函数封装,附完整代码
  • 终极英雄联盟效率工具:5分钟提升游戏表现的完整指南
  • AI驱动边界值测试实战:从原理到发现三大隐藏Bug
  • 保姆级教程:在Ubuntu 22.04上搞定USRP B200/B210与GNURadio 3.10的连接测试
  • AI赋能Nmap:构建智能安全扫描与自动化风险分析系统
  • 2026好用的视频去水印工具:电脑手机免费付费、在线网站全推荐
  • 高端机自动发评论速度记录
  • 长尾关键词在SEO优先策略中的有效应用与成效分析
  • 专业流媒体下载方案:N_m3u8DL-RE实现DASH/HLS/MSS内容高效保存
  • 如何一键永久保存你的微信记忆?WeChatMsg完全免费解决方案揭秘
  • Web Crypto API实战:AES-CBC加密逆向分析与Node.js复现
  • Mac系统下Jmeter接口压测实战:从环境搭建到性能分析
  • AgentScope 2.0
  • 低场MRI仿真系统设计与磁场不均匀性校正技术
  • AI 编程这事,已经开始变味了
  • 工业蒸汽流量计首选品牌:高精度与高稳定性双保障
  • 基于YOLO的目标检测论文高效改进策略:从注意力机制到工程实践
  • 计算机毕业设计之高校精品课网站
  • AVR单片机CCL与CRC模块实战:硬件逻辑与数据完整性设计
  • 别再手动移位了!用Verilog实现PRBS7并行输出(附10比特并行源码)