C++ 左值和右值

C++ 左值和右值

  • 左值、右值
  • 左值引用、右值引用
  • std::move()
    • std::move()的实现
    • 引用折叠
  • 完美转发
  • forward()的实现
  • 函数返回值是左值还是右值
  • 如何判断一个值是左值还是右值

左值、右值

在C++11中所有的值必属于左值、右值两者之一,右值又可以细分为纯右值、将亡值。在C++11中可以取地址的、有名字的就是左值,反之,不能取地址的、没有名字的就是右值(将亡值或纯右值)。举个例子,int a = b+c, a 就是左值,其有变量名为a,通过&a可以获取该变量的地址;表达式b+c、函数int func()的返回值是右值,在其被赋值给某一变量前,我们不能通过变量名找到它,&(b+c)这样的操作则不会通过编译。

在理解C++11的右值前,先看看C++98中右值的概念:C++98中右值是纯右值,纯右值指的是临时变量值、不跟对象关联的字面量值。临时变量指的是非引用返回的函数返回值、表达式等,例如函数int func()的返回值,表达式a+b;不跟对象关联的字面量值,例如true,2,”C”等。

C++11对C++98中的右值进行了扩充。在C++11中右值又分为纯右值(prvalue,Pure Rvalue)和将亡值(xvalue,eXpiring Value)。其中纯右值的概念等同于我们在C++98标准中右值的概念,指的是临时变量和不跟对象关联的字面量值;将亡值则是C++11新增的跟右值引用相关的表达式,这样表达式通常是将要被移动的对象(移为他用),比如返回右值引用T&&的函数返回值、std::move的返回值,或者转换为T&&的类型转换函数的返回值。

将亡值可以理解为通过“盗取”其他变量内存空间的方式获取到的值。在确保其他变量不再被使用、或即将被销毁时,通过“盗取”的方式可以避免内存空间的释放和分配,能够延长变量值的生命期。

左值引用、右值引用

左值引用就是对一个左值进行引用的类型。右值引用就是对一个右值进行引用的类型,事实上,由于右值通常不具有名字,我们也只能通过引用的方式找到它的存在

右值引用和左值引用都是属于引用类型,并且都是左值。无论是声明一个左值引用还是右值引用,都必须立即进行初始化。而其原因可以理解为是引用类型本身自己并不拥有所绑定对象的内存,只是该对象的一个别名。左值引用是具名变量值的别名,而右值引用则是不具名(匿名)变量的别名。

左值引用通常也不能绑定到右值,但常量左值引用是个“万能”的引用类型。它可以接受非常量左值、常量左值、右值对其进行初始化。不过常量左值所引用的右值在它的“余生”中只能是只读的。相对地,非常量左值只能接受非常量左值对其进行初始化。

int &a = 2;       # 左值引用绑定到右值,编译失败int b = 2;        # 非常量左值
const int &c = b; # 常量左值引用绑定到非常量左值,编译通过
const int d = 2;  # 常量左值
const int &e = c; # 常量左值引用绑定到常量左值,编译通过
const int &b =2;  # 常量左值引用绑定到右值,编程通过

右值值引用通常不能绑定到任何的左值,要想绑定一个左值到右值引用,通常需要std::move()将左值强制转换为右值,例如:

int a;
int &&r1 = c;             # 编译失败
int &&r2 = std::move(a);  # 编译通过

下表列出了在C++11中各种引用类型可以引用的值的类型。值得注意的是,只要能够绑定右值的引用类型,都能够延长右值的生命期。
在这里插入图片描述

std::move()

move作用是可以将一个左值转换成右值引用,从而可以调用C++11的拷贝构造函数。

std::move()的实现

std::move的实现主要依赖于static_cast<T&& >,但同时也会做一些参数推导(traits)的工作。其实现如下:

template<typename T>
typename remove_reference<T>::type&& move(T&& t)
{return static_cast<typename remove_reference<T>::type &&>(t);
}

对于t为右值的情况,有如下代码:

std::move(string("dengwen"));

首先模板类型推导确定T的类型为string,得remove_reference::type为string,故返回值和static的模板参数类型都为string &&,而move的参数就是string &&,于是不需要进行类型转换直接返回。

对于t为左值的情况,引入一条规则:当将一个左值传递给一个参数是右值引用的函数,且此右值引用指向模板类型参数(T&&)时,编译器推断模板参数类型为实参的左值引用。有如下代码:

string str("dengwen");
std::move(str);

此时明显str是一个左值,首先模板类型推导确定T的类型为string &,得remove_reference::type为string。故返回值和static的模板参数类型都为string &&,而move的参数类型为string& &&,折叠后为sting &。

所以结果就为将string &通过static_cast转为string &&。返回string &&。

引用折叠

  1. 所有右值引用折叠到右值引用上仍然是一个右值引用。(A&& && 变成 A&&)
  2. 所有的其他引用类型之间的折叠都将变成左值引用。 (A& & 变成 A&; A& && 变成 A&; A&& & 变成 A&)

完美转发

考虑下面例子:

template <typename T>
void func(T t) {cout << "in func" << endl;
}template <typename T>
void relay(T&& t) {cout << "in relay" << endl;func(t);
}int main() {relay(Test());
}

在这个例子当中,我们的期待是,我们在main当中调用relay,Test的临时对象作为一个右值传入relay,在relay当中又被转发给了func,那这时候转发给func的参数t也应当是一个右值。也就是说,我们希望:当relay的参数是右值的时候,func的参数也是右值;当relay的参数是左值的时候,func的参数也是左值

那么现在我们来运行一下这个程序,我们会看到,结果与我们预想的似乎并不相同:

default constructor
in relay
copy constructor
in func
destructor
destructor

我们看到,在relay当中转发的时候,调用了复制构造函数,也就是说编译器认为这个参数t并不是一个右值,而是左值,因为它有一个名字。那么如果我们想要实现我们所说的,如果传进来的参数是一个左值,则将它作为左值转发给下一个函数;如果它是右值,则将其作为右值转发给下一个函数,我们应该怎么做呢?

这时,我们需要std::forward<T>()。与std::move()相区别的是,move()会无条件的将一个参数转换成右值,而forward()则会保留参数的左右值类型。所以我们的代码应该是这样:

template <typename T>
void func(T t) {cout << "in func " << endl;
}template <typename T>
void relay(T&& t) {cout << "in relay " << endl;func(std::forward<T>(t));
}int main() {relay(Test());
}

现在运行的结果就成为了:

default constructor
in relay
move constructor
in func
destructor
destructor

而如果我们的调用方法变成:

int main() {Test t;relay(t);
}

那么输出就会变成:

default constructor
in relay
copy constructor
in func
destructor
destructor

完美地实现了我们所要的转发效果。

forward()的实现

std::forward()提供两个重载版本, 一个针对左值, 一个针对右值。

template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type& __t) noexcept
{ return static_cast<_Tp&&>(__t); }template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
{
static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
" substituting _Tp is an lvalue reference type");
return static_cast<_Tp&&>(__t);
}

根据以下实例进行分析:

template<typename T>
void foo(T&& fparam)
{
std::forward<T>(fparam);
}int i = 7;
foo(i);
foo(47);

在foo(i), 如果传入的是一个左值, 那么foo中T的类型将是int&, fparam类型是int& &&, 经过折叠为int&. 因此在std::forward模板函数中,推断出T的类型为int&,因此,std::remove_reference用int& 进行实例化。std::remove_reference的type成员是int。forward返回类型为int& &&, 折叠为int&。forward的参数类型__t为int&。static_cast<int & &&> 折叠为static_cast<int &>。

因此std::forward最终被实例化如下:

int &forward(int &__t){
return static_cast<int &>(__t)
}

可以发现,函数什么都不用做, 最终的传入forward的左值引用被保留了。

在foo(47)中, 传入的是一个右值,那么foo中T的类型将是int, fparam类型是T&&, 因此,在std::forward模板函数中推断出T的类型为int。因此, std::remove_reference用int 进行实例化。std::remove_reference的type成员是int。forward返回类型为int&&。forward的参数类型__t为int&&。static_cast<int && &&> 折叠为static_cast<int &&>
因此std::forward最终被实例化如下:

int &&forward(int &&__t){
return static_cast<int &&>(__t)
}

可以发现,函数什么都不用做, 最终的传入forward的右值引用被保留了。

通过以上分析, 实际上无论传递左值还是右值, forward都可以完美转发, 并且函数内部什么都不用做。

函数返回值是左值还是右值

  • 如果函数返回值是引用类型,则为左值。
  • 如果函数返回值是值类型,则为右值。

如何判断一个值是左值还是右值

右值是能够赋值给左值,但是左值不能赋值给右值。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/21560.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OpenShot 发布 2.3.2 版本啦!

导读OpenShot 2.3.3 发布了&#xff0c;Openshot 是 linux 的 Gnome 桌面一个非线性视频编辑器&#xff0c;有许多功能&#xff1a;你可以重划大小&#xff0c;修剪或者剪切视频&#xff0c;同时有实时的预览&#xff0c;图片覆盖&#xff0c;标题模板&#xff0c;视频解码&…

Exploring the Underlying Architecture of CSS3

引言 在现代的网页设计中&#xff0c;CSS&#xff08;层叠样式表&#xff09;起着至关重要的作用。CSS3作为最新的CSS标准&#xff0c;引入了许多令人兴奋的功能和特性。但是&#xff0c;要真正理解CSS3的底层架构实现原理&#xff0c;对于前端开发者来说&#xff0c;是非常重…

web前端框架Javascript之JavaScript 异步编程史

早期的 Web 应用中&#xff0c;与后台进行交互时&#xff0c;需要进行 form 表单的提交&#xff0c;然后在页面刷新后给用户反馈结果。在页面刷新过程中&#xff0c;后台会重新返回一段 HTML 代码&#xff0c;这段 HTML 中的大部分内容与之前页面基本相同&#xff0c;这势必造成…

PHP 前后端分离,运行配置

H5 WEB目录:安装 yarn install、npm install &#xff08;依赖包&#xff09; 在电脑&#xff1a;安装nodejs Composer下载 &#xff1a;https://getcomposer.org/

python 计算图片hash 缓存图片为key

python&#xff0c;有时希望缓存图片作为key&#xff0c;怎么办&#xff1f;缓存整张突破占用内存太多&#xff0c;不妨缓存hash值&#xff1a; Fast way to Hash Numpy objects for Caching import hashlib import numpy a numpy.random.rand(10, 100) b a.view(numpy.uin…

性能测试,python 内存分析工具 -memray

Memray是一个由彭博社开发的、开源内存剖析器&#xff1b;开源一个多月&#xff0c;已经收获了超8.4k的star&#xff0c;是名副其实的明星项目。今天我们就给大家来推荐这款python内存分析神器。 ​ Memray可以跟踪python代码、本机扩展模块和python解释器本身中内存分配&#…

考完软考,有什么备考心得和学习经验可以分享吗?

我考过信息系统项目管理师&#xff0c;可以给你一些备考建议。这是软考高级资格考试的一门科目&#xff0c;考试包括三个部分。如果你觉得难度较大&#xff0c;可以先从系统集成考试开始&#xff0c;积累经验再去考高级科目。考试内容包括信息系统项目管理综合知识、信息系统项…

A TupleBackedMap cannot be modified Mybatis分页,使用List<Map>接参,无法修改map的解决方案

问题描述 当使用Mybatis 进行Page分页&#xff0c;再使用Page< map >作为接受参数。此时尝试修改map则会报错。 报错为 java.lang.UnsupportedOperationException: A TupleBackedMap cannot be modified解决方案 使用新的数组&#xff0c;使用反射&#xff0c;构建工具…

C语言中char、short、int、long各占多少字节

1byte 8bit 一个字节占8个二进制位 windows操作系统&#xff0c;32位机中&#xff0c; char&#xff1a; 1个字节 short&#xff1a; 2个字节 int&#xff1a; 4个字节 long&#xff1a; 4个字节 以下是windows操作系统&#xff0c;32位机下的代码测试结果&#xff08;3…

深入理解机器学习与极大似然之间的联系

似然函数&#xff1a;事件A的发生含着有许多其它事件的发生。所以我就把这些其它事件发生的联合概率来作为事件A的概率&#xff0c;也就是似然函数。数据类型的不同&#xff08;离散型和连续性&#xff09;就有不同的似然函数 极大似然极大似然估计方法&#xff08;Maximum Li…

iOS技术之 手机系统15.0之后 的 UITableView section header多22像素问题

iOS 15 的 UITableView又新增了一个新属性&#xff1a;sectionHeaderTopPadding 会给每一个section header 增加一个默认高度&#xff0c;当我们 使用 UITableViewStylePlain 初始化 UITableView的时候&#xff0c;就会发现&#xff0c;系统给section header增高了22像素。 解…

MongoDB文档--基本安装-linux安装(mongodb环境搭建)-docker安装(挂载数据卷)-以及详细版本对比

阿丹&#xff1a; 前面了解了mongodb的一些基本概念。本节文章对安装mongodb进行讲解以及汇总。 官网教程如下&#xff1a; 安装 MongoDB - MongoDB-CN-Manual 版本特性 下面是各个版本的选择请在安装以及选择版本的时候参考一下&#xff1a; MongoDB 2.x 版本&#xff1a…

深入学习 Redis - 基于 Jedis 通过 Java 客户端操作 Redis

目录 一、Jedis 依赖 二、Java 客户端操控 redis 2.1、准备工作&#xff08;ssh 隧道&#xff09; 2.2、概要 2.2、string 2.3、hash 2.4、list 2.5、set 2.5、zset 一、Jedis 依赖 自己去 中央仓库 上面找. 二、Java 客户端操控 redis 2.1、准备工作&#xff08;ssh 隧…

51单片机(普中HC6800-EM3 V3.0)实验例程软件分析 实验二 LED闪烁

目录 前言 一、原理图及知识点介绍 二、代码分析 知识点四&#xff1a;delay(u16 i)这个函数为什么i1时&#xff0c;大约延时10us&#xff1f; 前言 已经是第二个实验了&#xff0c;上一个实验是点亮第一个LED灯&#xff0c;这个实验是LED的闪烁。 一、原理图及知识点介绍…

由红黑树到map/set

文章目录 一.map/set 的封装思路1.封装思路2.红黑树节点调整3.map 和 set 的定义4.仿函数 KeyOfValue5.map/set 的插入 二.map/set 迭代器实现1.迭代器的定义2.解引用运算符重载3.成员访问运算符重载4.(不)等于运算符重载5.begin() 与 end()6. 运算符重载7.-- 运算符重载8.[ ]下…

ELK常用语法和线上问题排查

ELK常用语法及线上问题排查 速查问题 1.全文搜索 在搜索栏输入目标值&#xff0c;查询所有字段中包含该值的文档 案例&#xff1a;用户反馈了一串id或者其他同事给了一个jobid&#xff0c;还没有头绪怎么查的时候&#xff0c;直接全局搜索 jobid 2.字段 限定字段全文搜索…

CSS学习记录(基础笔记)

CSS简介: CSS 指的是层叠样式表* (Cascading Style Sheets)&#xff0c;主要用于设置HTML页面的文字内容&#xff08;字体、大小、对齐方式&#xff09;&#xff0c;图片的外形&#xff08;边框&#xff09; CSS 描述了如何在屏幕、纸张或其他媒体上显示 HTML 元素 CSS 节省…

接口抓包,Fiddler抓包使用方法总结,入门到精通辅助实战...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 工作原理 Fiddle…

Docker网络模式详解

目录 Docker网络模式 一、Host模式 二、container模式 三、none模式 四、bridge模式 五、Overlay模式 Docker网络模式 安装Docker时会自动创建3个网络&#xff0c;可以使用docker network ls命令列出这些网络。 [rootdocker ~]# docker network ls 我们在使用docker run…

小程序服务器配置多大够用?

​  了解小程序服务器的大小和要求对于确保小程序的高效运行非常重要。下面将介绍小程序服务器的大小和要求&#xff0c;帮助您选择合适的服务器。 服务器费用 服务器费用因服务器类型、配置和带宽等因素而异。一般而言&#xff0c;小型小程序服务器的年费用在500元至2000元之…