C++ 左值和右值

C++ 左值和右值左值和右值左值 右值左值引用 右值引用 std move std move 的实现引用折叠左值 右值在 C 11 中所有的值必属于左值 右值两者之一 右值又可以细分为纯右值 将亡值 在 C 11 中可以取地址的 有名字的就是左值 反之 不能取地址的 没有名字的就是右值 将亡值或纯右值 举个例子 inta b c a 就是左值 其有变量名为 a 通过 amp a 可以获取该变量的地址 表达式 b c 函数 intfunc 的返回值是右值 在其被赋值给某一变量前 我们不能通过变量名找到它 b c

左值、右值

在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); # 编译通过 

std::move()

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

std::move()的实现

std::move的实现主要依赖于static_cast

,但同时也会做一些参数推导(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

()
与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

折叠为static_cast



因此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

折叠为static_cast


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




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

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

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

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

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

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

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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/216580.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月18日 上午11:32
下一篇 2026年3月18日 上午11:33


相关推荐

  • UCML工作流使用案例

    UCML工作流使用案例案例背景 申请者填写申请单 启动流程 如果不大于 10 万元 部门经理审批 同意则告知申请人 不同意则流程直接结束 如果大于 10 万元 总经理审批 同意则告知申请人 不同意则流程直接结束 Step1 UCML 工作流业务准备 1 右键数据表 添加工作流业务的字段 并执行 创建表 与 数据访问源码生成 2 创建 BC 添加业务组件 3 创建 VC 添加业务视图组件 VC 需要创建

    2026年3月19日
    1
  • [Elasticsearch]查询语法速查

    [Elasticsearch]查询语法速查0x01 基本语法基本语法是 GET POST http domain com you index name type1 type2 search search type count scan 注意 随着 ES 版本变化 搜索语法也有小调整 本文以 1 7 为准 全基于 rest 式 http 调用 其中 GET 方法支持在 body 传参数 search 是关键字 以此结束表示搜索行为 可以

    2026年3月18日
    1
  • 【即梦AI教程】19.AI特效制作

    【即梦AI教程】19.AI特效制作

    2026年3月12日
    2
  • PAD图初认识_认识数字图片

    PAD图初认识_认识数字图片程序流程图&N-S图&PAD图程序流程图任何复杂的程序图都应由5种基本控制结构组成或嵌套而成。 盒图(N-S图)Nassi和Scheiderman提出了一种符合结构化程序设计原则的图形描述工具,叫作盒图,也叫做N-S图。任…

    2022年8月13日
    15
  • 打印机smtp服务器地址还未配置_打印机如何添加邮箱地址

    打印机smtp服务器地址还未配置_打印机如何添加邮箱地址打印机smtp服务器设置方法内容精选换一换设置日志级别。参见准备环境完成环境配置。以运行用户登录安装Toolkit组件的服务器。执行命令,设置日志级别、获取日志文件。adc–hostxx.xx.xx.xx:22118–log’SetLogLevel(0)[error]’adc–hostxx.xx.xx.xx:22118–log’SetLogLevel(1本节介绍如何基于迁…

    2022年10月7日
    4
  • Hadoop-2.2.0中国文献——MapReduce 下一代 —配置单节点集群

    Hadoop-2.2.0中国文献——MapReduce 下一代 —配置单节点集群

    2022年1月17日
    43

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号