C++ 左值和右值

这篇具有很好参考价值的文章主要介绍了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);  # 编译通过

下表列出了在C++11中各种引用类型可以引用的值的类型。值得注意的是,只要能够绑定右值的引用类型,都能够延长右值的生命期。
C++ 左值和右值,c/c++,c++,java,jvm

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都可以完美转发, 并且函数内部什么都不用做。

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

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

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

右值是能够赋值给左值,但是左值不能赋值给右值。文章来源地址https://www.toymoban.com/news/detail-625018.html

到了这里,关于C++ 左值和右值的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • [开发语言][c++]:左值、右值、左值引用、右值引用和std::move()

    写在前面: 如果你也被 左值、右值、左值引用、右值引用和std::move 搞得焦头烂额,相关概念和理解不够深入,或者认识模棱两可,那么这篇文章将非常的适合你,耐心阅读,相信一定会有所收获~~ 左值: 可以取地址、位于等号左边 – 表达式结束后依然存在的持久对象

    2024年02月02日
    浏览(62)
  • C++左值右值完美转发转移

    英文含义: 左值(Lvalue) : Locator value ,意味着它指向一个具体的内存位置。 右值(Rvalue) : Read value ,指的是可以读取的数据,但不一定指向一个固定的内存位置。 定义 左值 :指的是一个持久的内存地址。左值可以出现在赋值操作的左侧或右侧。例如,变量、数组的元

    2024年03月10日
    浏览(54)
  • C++ 学习系列 1 -- 左值、右值与万能引用

    简单的说,左值可以放在等号的左边,右值可以放在等号的右边。 左值可以取地址,右值不能取地址。 1.1 左值举例: 变量、函数或数据成员 返回左值引用的表达式 如 ++x、x = 1、cout \\\' \\\'  int x = 0 1.2 右值举例: 返回非引用类型的表达式 如 x++、x + 1 除字符串字面量之外的字面

    2024年02月14日
    浏览(43)
  • 初识C++之左值引用与右值引用

    目录 一、左值引用与右值引用 1. 左值和右值的概念 1.1 左值 1.2 右值  1.3 左值与右值的区分 2. 左值引用与右值引用 2.1 左值引用与右值引用的使用方法 2.2 左值引用的可引用范围 2.3 右值引用的可引用范围 3. 右值引用的作用 3.1 减少传值返回的拷贝 3.2 插入时的右值引用 4

    2023年04月26日
    浏览(40)
  • C++面试八股文:什么是左值,什么是右值?

    某日二师兄参加XXX科技公司的C++工程师开发岗位第16面: 面试官:什么是左值,什么是右值? 二师兄:简单来说,左值就是可以使用 符号取地址的值,而右值一般不可以使用 符号取地址。 二师兄:一般左值存在内存中,而右值存在寄存器中。 二师兄:严格意义上分,右值

    2024年02月09日
    浏览(41)
  • C++右值引用(左值表达式、右值表达式)(移动语义、完美转发(右值引用+std::forward))(有问题悬而未决)

    在 C++ 中,表达式可以分为左值表达式和右值表达式。左值表达式指的是可以出现在赋值语句左边的表达式,例如变量、数组元素、结构体成员等;右值表达式指的是不能出现在赋值语句左边的表达式,例如常量、临时对象、函数返回值等。 右值是指将要被销毁的临时对象或

    2024年02月04日
    浏览(44)
  • MordernC++之左值(引用)与右值(引用)

    C++中左值与右值的概念是从C中继承而来,一种简单的定义是 左值能够出现再表达式的左边或者右边,而右值只能出现在表达式的右边 。 另一种区分左值和右值的方法是: 有名字、能取地址的值是左值,没有名字、不能取地址的值是右值 。比如上述语句中a,b, c是变量可以

    2023年04月09日
    浏览(41)
  • 【C语言深入】细聊C语言中的“左值”和“右值”

    左值就是那些可以出现在赋值符号左边的东西,它标识了一个可以存储结果值的地点。 程序在编译时,编译器会为每个变量分配一个地址(左值),这个地址在编译是即可知。 也就是说,左值在编译时即可知,左值标志存储结果的一个地方,也可以理解为左值就是一块空间。

    2024年02月14日
    浏览(45)
  • 左值引用、右值引用,std::move() 的汇编解释

    1:左值引用 引用其实还是指针,但回避了指针这个名字。由编译器完成从地址中取值。以vs2019反汇编: 如图,指针和引用的汇编代码完全一样。但引用在高级语言层面更友好,对人脑。比如可以少写一个 * 号和 - 。 ,以下是指针和引用的使用: 以上就是左值引用,引用的

    2024年02月04日
    浏览(39)
  • C++右值引用,右值引用与const引用的区别

    左值:可以取地址的、有名字的变量,有持久性; 右值:一般是不可寻址的常量,或在表达式求值过程中创建的无名临时对象,短暂性的。 C++11新增了另一种引用——右值引用。这种引用可指向右值,使用声明。 右值引用只能引用临时变量和常量值。 const引用:可以引用普

    2024年01月18日
    浏览(64)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包