左值与右值

判别:

  • 左值:用来存储数据的变量,有实际的内存地址,表达式结束后任然存在。
  • 右值:匿名的临时变量,表达式结束时被销毁,不能存放数据,可以被修改或者不修改;字面常量也是右值。
int x = 10;
int* p = &++x;  // 正确,前置++返回左值
int* q = &x++;  // 错误,后置++返回临时对象

左值引用就是普通的引用,下面介绍右值引用:

#include <iostream>
using namespace std;int main() {int x = 1;int&& r = x++;cout << r << endl;   // 1cout << x << endl;   // 2x = 10;cout << r << endl;   // 1return 0;
}

引用了临时的右值对象,相当于延长了生命周期。

std::move()移动

转移语义,使用的std::move()进行转移对象,把一个对象(左值)转移成匿名的右值。它的意义在于减少不必要的拷贝操作,提高程序性能。本质上,std::move是使得对象的所有权发生了转移,给出代码实例:

#include <iostream>
#include <vector>
#include <string>int main() {std::string str = "Hello";std::vector<std::string> v;// 这里是对str的一个拷贝,拷贝了字符串的副本到vector中v.push_back(str);   std::cout << "After copy, str is \"" << str << "\"\n";// 这里是直接把str内容转移到vector中,没发生任何拷贝// 原来的左值被转移后,str又成为了空串v.push_back(std::move(str)); std::cout << "After move, str is \"" << str << "\"\n";std::cout << "The contents of the vector are \"" << v[0] \<< "\", \"" << v[1] << "\"\n";return 0;
}
/*
代码输出:
After copy, str is "Hello"
After move, str is ""
The contents of the vector are "Hello", "Hello"
*/

上述代码解决了一个效率问题,每次不用拷贝一遍字符串到vector中,而是直接使用原来的字符串。注意到,容器的本身不能存储元素的引用,这在处理复杂对象的时候,会进行大量的拷贝,浪费时间和内存;但是,借助于std::move可以直接把左值对象的值进行转移,从而省去大量的复制步骤!

对于自定义的对象,需要显示的说明转移构造函数,代码实例:

#include <iostream>
#include <vector>
#include <string>class Node {public:// 默认无参数构造函数Node() {std::cout << "default construction" << std::endl;a = 0;str = "";p = nullptr;}//  普通的拷贝函数Node(const Node& node) {std::cout << "copy construction" << std::endl;a = node.a;str = node.str;p = node.p;}// 移动构造函数,注意不能声明为const !!! 为确保安全,声明为不抛出异常的,// 移动失败后直接退出程序,否则有悬空指针是非常危险的。Node(Node&& node) noexcept {std::cout << "move construction" << std::endl;a = std::move(node.a);str = std::move(node.str); // 注意使用move提高效率,string类型是可以直接move的p = node.p;node.p = nullptr;  // 置空原来的指针}// 析构函数~Node() {std::cout << "deconstruction" << std::endl;a = 0;str.clear();if(!p) {delete p;p = nullptr;}}int a;int* p;std::string str;
};int main() {Node n; // 正常的默认构造函数n.str = "hello world !";n.a = 10;n.p = new int(10);Node n1(n);                // 这里执行拷贝操作std::vector<Node>vec;vec.push_back(std::move(n1)); // 这里执行move操作std::cout << "n.str= " << n.str << std::endl;std::cout << "n1.str= " << n1.str << std::endl;std::cout << "vec.str= " << vec[0].str << std::endl;return 0;
}
/*
输出结果:
default construction
copy construction
move construction
n.str= hello world !
n1.str=
vec.str= hello world !
deconstruction
deconstruction
deconstruction
*/

说明一点,std::move完成后,原来的左值不会立刻析构,而是正常流程的结束并析构。

std::forward完美转发

C++的std::forward完美转发,在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数。如果传入的参数是不是左值引用,那么返回一个参数右值的引用;如果参数是左值引用,那么返回左值的引用。

转换到右值引用使用std::move,其余情况使用完美转发。

完美转发的一般用途:

template<typename T>
void IamForwording(T t) {IrunCodeActually(t);
}

上面的代码模板说明,IamForwording函数的用途只是把模板参数t传入进来,而IrunCodeActually是真正执行的函数,该函数希望原封不动传递前者传入参数的类型。

为了处理各种参数的匹配关系,C++引入了参数折叠规则,给出编译推断的策略:

T& + & => T&
T&& + & => T&
T& + && => T&
T&& + && => T&&

+左侧是函数形参表示的形式,+右侧是实际传入参数的形式,=>后表示实际推断的形式。

虽然组合方式很多,但是有一个规律:只有形参和传入的参数同时是右值时,才会推断成右值引用;否则一律是左值

因此,引入std::forward来解决这个问题。

template <class T> T&& forward (typename remove_reference<T>::type& arg) noexcept;
template <class T> T&& forward (typename remove_reference<T>::type&& arg) noexcept;

函数的返回值是:如果arg是左值,就返回左值;否则一律返回右值。

给出一个简单的例子:

给出C++参考的实例测试:

#include <utility>
#include <iostream>void overloaded(const int& x) {std::cout << "lvalue\n";
}void overloaded(int&& x) {std::cout << "rvalue\n";
}template<typename T>
void fn(T&& x) {overloaded(x);overloaded(std::forward<T>(x));
}int main() {int a;std::cout << "calling fn with lvalue:\n";fn(a);std::cout << "calling fn with rvalue:\n";fn(0);return 0;
}
/*
输出结果:
calling fn with lvalue:
lvalue
lvalue
calling fn with rvalue:
lvalue
rvalue
*/

从结果可以看出,使用了std::forward的函数参数才能原封不动的传递原来数据的类型。这样可以根据参数的类型,自动的进行不同的重载。

C++左值与右值,移动与完美转发相关推荐

  1. C++/C++11中左值、左值引用、右值、右值引用的使用

    C++的表达式要不然是右值(rvalue),要不然就是左值(lvalue).这两个名词是从C语言继承过来的,原本是为了帮助记忆:左值可以位于赋值语句的左侧,右值则不能. 在C++语言中,二者的区别就没 ...

  2. C/C++左值性精髓(二)哪些表达式是左值,哪些是右值?----右值表达式

    2019独角兽企业重金招聘Python工程师标准>>> C对于右值的定义是表达式的值,C中所有完整表达式的结果都是右值.所谓完整表达式(full expression),指的是这样的 ...

  3. 【 C 】对左值与右值的一些个人思考

    今天重温C语言的指针,看的书是<C和指针>,关于左值和右值以及指针表达式的内容看得甚是迷惑与煎熬,怎么会这么难理解,指针表达式又是作为左值又一会作为右值,而且二者有着不一样的含义,为什么当 ...

  4. c/c++左值和右值

    C/C++中的变量有左值和右值之分,他们的区别主要如下: (1)左值可以放在赋值号 = 的左右两边,右值只能放在赋值号 = 的右边 (2)在C语言中,有名字的变量即为左值:而函数的运行结果或表达式中间 ...

  5. 《C++语言入门经典》一2.8 左值与右值

    2.8 左值与右值 C++中的每个语句.表达式的结果分为左值与右值两类.左值指的是内存中持续存储的数 据,而右值是指临时存储的结果. 在程序中,声明过的独立变量如: Int k; short p; c ...

  6. 39.左值、左值引用、右值、右值引用

    1.左值和右值的概念 左值是可以放在赋值号左边可以被赋值的值:左值必须要在内存中有实体:          右值当在赋值号右边取出值赋给其他变量的值:右值可以在内存也可以在CPU寄存器.       ...

  7. 2020-10-27(左值和右值)

    什么是表达式: 表达式由一个或多个操作数通过操作符组合而成.最简单的表达式仅包含一个字面值常量.较复杂的表达式则由操作符以及一个或者多个操作数构成. 一个变量是表达式但是一个表达式就不一定是变量了. ...

  8. c++中的左值与右值

    转载自 http://www.cnblogs.com/catch/p/3500678.html 左值 (lvalue)和右值 (rvalue) 是 c/c++ 中一个比较晦涩基础的概念,有的人可能甚至 ...

  9. c语言 变量的左值和右值,C++雾中风景10:聊聊左值,纯右值与将亡值

    C++11的版本在类型系统上下了很大的功夫,添加了诸如auto,decltype,move等新的关键词来简化代码的编写与降低阅读代码的难度.为了更好的理解这些新的语义,笔者确定通过几篇文章来简单窥探一 ...

  10. 左值、右值、左值引用、右值引用

    1. 左值 左值(lvalue,left value),顾名思义就是赋值符号左边的值,可以取地址.准确来说,左值是表达式(不一定是赋值表达式)后依然存在的持久对象. 可以将左值看作是一个关联了名称的内 ...

最新文章

  1. mysql数据库忘记密码
  2. python之初接触
  3. csdn博客怎么修改字体的大小和颜色
  4. html前端 echarts图表使用详解
  5. 如何以大数据的JAX-RS响应的形式将JPA结果流化/序列化
  6. nuxt页面跳转_nuxt 项目如何解决组件复用时页面不刷新的问题
  7. 学生选课管理系统c语言程序报告,学生选课管理系统c语言程序
  8. java 递归层级拼接_使用递归方法拼接层级树
  9. 显控触摸屏编程手册_深圳显控AKWORKSHOP触摸屏与ALLENBRADLEY通讯手册.pdf
  10. Python按页拆分Word文档
  11. 看到一些关于前端的书籍或者好的网站推荐
  12. 【戏言、昔言、惜言】谭惜言写了一辈子的戏,真情假意,全在戏言里。
  13. Charles 安装证书及抓包
  14. js如何获取滚动条的高度
  15. 线段树染色问题(例题为poj2777)
  16. Mysql给一个大表加一列_MySQL 大表添加一列的实现
  17. 大整数乘法(Karatsuba算法的字符串形式的C++实现)
  18. win10新建虚拟机网络配置未连接服务器,win10虚拟主机怎么联网(win10虚拟机连不上网)...
  19. python 人脸识别:从入门到精通 (5.4)常用的神经网络层
  20. Oracle表空间清理

热门文章

  1. c语言延时函数delay_STM32中精确延时函数的实现
  2. 书籍分析实例:哈利波特的分词及人物关系
  3. 修改caffe源码--支持多标签--关键点检测
  4. Python之 while循环
  5. Python中无法使用“~”获取Ubuntu系统的用户目录
  6. Tensorflow高级封装
  7. POJ2155 Matrix二维线段树经典题
  8. Intellij IDEA基本配置
  9. 【设计模式】设计模式六大原则
  10. ThinkPHP删除指定文件(物理删除)