文档章节

C++11中的右值引用

临峰不畏
 临峰不畏
发布于 2017/05/07 22:27
字数 1267
阅读 27
收藏 1

C++11中定义了右值引用的概念,这是一个比较实用,但不好理解的内容。

右值引用,顾名思义,就是引用右值。
那什么是右值呢?书的给出的解释是:可以取地址的,有名字的就是左值。反之,为右值。

T a(1);
T b(a);

其中 a是左值,b也是右值。
 

T ReturnRValue() {
    return T(3);
}
T c = ReturnRValue()

上面这个函数调用返回过程中,首先 T(3) 生成了一个对象,然后将这个对象赋值给tmp对象,T tmp = T(3);在ReturnRValue()函数返回后,会有个 T c = tmp 的动作。这个过程中,tmp,T(3) 都是右值。

左值与右值比较,比较明显的区别是:
右值是自动产生的,不被用户直接操作的,没有名称的,将立即被销毁的对象。
左值则是有名称的,能够被操作的对象。

int a; // a左值
int b = a + 1;  // (a + 1)是右值,b是左值

那为什么出来右值引用的概念呢?

因为我们用左值引用的方式对访问右值,是不能对其进行修改的。如:

const T &d = ReturnRValue();  //! 通过编译
T &d = ReturnRValue();   //! 编译错误
error: invalid initialization of non-const reference of type ‘T&’ from an rvalue of type ‘T’

如果我们想要操作右值呢?那么必须要用右值引用。

T &&d = ReturnRValue();  //! C++11上通过编译

之后,可以像访问普通变量一样去访问变量d。

右值引用让我们在接收到右值参数的时候,可以操作去操作右值的内容。在复制构造与赋值过程中,如果可以操作右值,那么可以将右值中的资源直接转换到新的对象里,从而减免了申请资源,再复制资源的操作过程。

左值构造:

T(const T &src) { //! 左值构造(复制)
  cout << "T(const T &):" << this << "<--" << &src << endl;
  _big_block = new char [1024];
  memcpy(_big_block, src._big_block, BLOCK_SIZE);
}

右值构造:

T(T &&src) { //! 右值构造(移动)
  cout << "T(T &&):" << this << "<--" << &src << endl;
  _big_block = src._big_block;
  src._big_block = NULL;
}

如下为整体的试验代码:
 

#include <iostream>
#include <cstring>
using namespace std;

#define BLOCK_SIZE 1024

struct T {
  T() { //! 构造
    cout << "T():" << this << endl;
    _big_block = new char [BLOCK_SIZE];
  }

  T(const T &src) { //! 左值构造(复制)
    cout << "T(const T &):" << this << "<--" << &src << endl;
    _big_block = new char [1024];
    memcpy(_big_block, src._big_block, BLOCK_SIZE);
  }

  ~T() { //! 析构
    delete [] _big_block;
    cout << "~T():" << this << endl;
  }

  T& operator = (const T &src) { //! 左值赋值(复制)
    cout << "operator=(const T&):" << this << "<--" << &src << endl;
    if (_big_block != NULL)
      delete [] _big_block;

    _big_block = new char [BLOCK_SIZE];
    memcpy(_big_block, src._big_block, BLOCK_SIZE);
  }

  T(T &&src) { //! 右值构造(移动)
    cout << "T(T &&):" << this << "<--" << &src << endl;
    _big_block = src._big_block;
    src._big_block = NULL;
  }

  T& operator = (T &&src) { //! 右值赋值(移动)
    cout << "operator=(T &&):" << this << "<--" << &src << endl;
    if (_big_block != NULL)
      delete [] _big_block;

    _big_block = src._big_block;
    src._big_block = NULL;
  }

private:
  char *_big_block;
};

/////////////////////////////////////////////////////
T ReturnRvalue() { //! 返回一个T对象
  return T();
}

void AcceptT(const T &) { //! 接受左值引用
  cout << "Accept(const T &)" << endl;
}

void AcceptT(T &&) { //! 接受右值引用
  cout << "Accept(T &&)" << endl;
}

/////////////////////////////////////////////////////
int main() {
  cout << "> T a" << endl;
  T a;
  cout << "> T b = a" << endl;
  T b = a; //! 左值构造
  cout << "> T c" << endl;
  T c;
  cout << "> c = a" << endl;
  c = a; //! 左值赋值

  cout << ">" << endl;
  cout << ">" << endl;

  cout << "> T d = ReturnRvalue()" << endl;
  T d = ReturnRvalue(); //! 以右值构造d
  cout << "> T && e = ReturnRvalue()" << endl;
  T && e = ReturnRvalue(); //! 定义e引用右值,而e本身是左值
  cout << "> T f = e" << endl;
  T f = e; //! 以左值e构造f,e是左值
  cout << "> d = ReturnRvalue()" << endl;
  d = ReturnRvalue(); //! 右值赋值

  cout << ">" << endl;
  cout << ">" << endl;

  cout << "> AcceptT(a)" << endl;
  AcceptT(a); //! 接受左值
  cout << "> AcceptT(ReturnRvalue())" << endl;
  AcceptT(ReturnRvalue()); //! 接受右值
  cout << ">" << endl;
  return 0;
}

编译命令:g++ -o test test.cpp -std=c++11 -fno-elide-constructors

运行结果:

> T a
T():0x7ffd354d68c0
> T b = a
T(const T &):0x7ffd354d68b0<--0x7ffd354d68c0  --a是左值,所以采用左值复制构造函数
> T c
T():0x7ffd354d68a0
> c = a
operator=(const T&):0x7ffd354d68a0<--0x7ffd354d68c0  --a是左值,所以采用左值赋值函数
>
>
> T d = ReturnRvalue()
T():0x7ffd354d6850   --创建T()对象
T(T &&):0x7ffd354d68d0<--0x7ffd354d6850  --将T()移动到tmp对象,由于T()为右值,所以用右值移动构造函数
~T():0x7ffd354d6850  --析构T()对象
T(T &&):0x7ffd354d6890<--0x7ffd354d68d0  --将tmp移动到d对象,由于tmp为右值,所以用右值移动构造函数
~T():0x7ffd354d68d0  --析构tmp对象
> T && e = ReturnRvalue()
T():0x7ffd354d6850   --创建T()对象
T(T &&):0x7ffd354d68e0<--0x7ffd354d6850  --将T()移动到tmp对象,由于T()为右值,所以用右值移动构造函数
~T():0x7ffd354d6850  --析构T()对象,e引用的是tmp对象
> T f = e
T(const T &):0x7ffd354d6880<--0x7ffd354d68e0   --e虽然代表的是tmp对象,但e会被继续访问,所以是左值,这里采用的是左值复制构造函数
> d = ReturnRvalue()
T():0x7ffd354d6850
T(T &&):0x7ffd354d68f0<--0x7ffd354d6850  --将tmp移动到d对象,由于tmp为右值,所以用右值移动赋值函数
~T():0x7ffd354d6850
operator=(T &&):0x7ffd354d6890<--0x7ffd354d68f0
~T():0x7ffd354d68f0
>
>
> AcceptT(a)
Accept(const T &)  --a是左值
> AcceptT(ReturnRvalue())
T():0x7ffd354d6850
T(T &&):0x7ffd354d6900<--0x7ffd354d6850
~T():0x7ffd354d6850
Accept(T &&)  --ReturnRValue()返回的是右值
~T():0x7ffd354d6900
>
~T():0x7ffd354d6880  --左值对象析构,不解析
~T():0x7ffd354d68e0
~T():0x7ffd354d6890
~T():0x7ffd354d68a0
~T():0x7ffd354d68b0
~T():0x7ffd354d68c0

使用右值引用可以提升赋值效率,避免临时变量赋值或构造过程中没有必要的复制过程。

© 著作权归作者所有

临峰不畏
粉丝 218
博文 187
码字总数 98583
作品 0
深圳
架构师
私信 提问
C++primer标准库(2)

作为大四应届生o( ̄︶ ̄)o 最近为了后续的面试工作地点看C++primer再次深入学习C++写里一点笔记: 如下: 1. io库 • **istream(输入流)类提供输入操作。 • ostream(输出流)类提供输出操作。 ...

微小的鱼233
2018/03/02
0
0
cannot convert ‘a’ (type ‘int’) to type ‘int&&’

背景 启用C++11编译老代码的时候,g++报错。示例代码如下: #include <utility> int main() { int a = 0; auto b = std::make_pair<int, int>(a, 1); return 0;} 出错信息: test.cpp: In f......

kyos
2015/03/14
0
0
c++ primer 第五版学习笔记

第二章 函数体外定义的内置类型变量会初始化为0,函数体外的是未初始化的 用constexpr声明变量表示它是一个常量表达式(编译器可以确定的值),且只能应用于字面值 c++11中可以用 来定义一个...

David栗子
2017/12/11
0
0
C++11 标准新特性: 右值引用与转移语义

C++11 标准新特性: 右值引用与转移语义 C++ 的新标准 C++11 已经发布一段时间了。本文介绍了新标准中的一个特性,右值引用和转移语义。这个特性能够使代码更加简洁高效。 新特性的目的 右值引...

雅各宾
2014/01/17
0
0
揭开C++移动与复制的神秘面纱

摘要:本次分享主要围绕C++中的移动与复制问题,讲解了移动与复制过程中涉及的一系列概念,具体场景中存在的问题以及解决方案。帮助大家深入学习C++中移动与复制,并解决实际问题。 演讲嘉宾...

斑马不睡觉
2018/04/18
0
0

没有更多内容

加载失败,请刷新页面

加载更多

九、RabbitMQ的集群安装

概述 理解RabbitMQ的集群原理可能需要花点功夫,但是配置RabbitMQ的集群则非常容易。 注意 如果有防火墙,请提前开放相关端口: client端通信口5672 管理口15672 server间内部通信口25672 e...

XuePeng77
22分钟前
1
0
今天的学习

今天学到了用ci框架向数据库添加数据,代码是这样的: $picture = $this->input->post('picture');$price = $this->input->post('price');$name = $this->input->post('name');$standa......

墨冥
30分钟前
1
0
Java agentlib参数分析

Java agentlib参数分析 再用intellij idea进行远程调试的时候,具体的配置选项如下: 标红的一行显示了远程调试需要添加的虚拟机参数。这个参数到底有什么意义? 我在命令行输入java命令,输...

Mr_Tea伯奕
47分钟前
2
0
四种软件架构演进史,程序员会一种就很牛了!

如果一个软件开发人员,不了解软件架构的演进,会制约技术的选型和开发人员的生存、晋升空间。这里我列举了目前主要的四种软件架构以及他们的优缺点,希望能够帮助软件开发人员拓展知识面。 ...

我最喜欢三大框架
52分钟前
6
0
如何做高可用的架构设计?

定义目标 既然我们的目标是做到高可用,那么我们就有必要先明确清楚高可用的含义,并通过拆解目标,让目标可以被量化。按照我的理解,可以将目标按照以下三条进行拆解: 1. 保持业务高稳定性...

别打我会飞
52分钟前
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部