C++11(左值(引用),右值(引用),移动语义,完美转发)

简介: C++11(左值(引用),右值(引用),移动语义,完美转发)



一、左值与左值引用

1、左值

左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋值,左值可以出现赋值符号的左边,也可以出现在右边。

以下的p、b、c、*p都是左值:

int* p = new int(0);
int b = 1;
const int c = 2;

注:定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。

2、左值引用

左值引用就是给左值的引用,给左值取别名。

以下几个是对上面左值的左值引用:

int*& rp = p;
int& rb = b;
const int& rc = c;
int& pvalue = *p;

总结:

1、左值引用只能引用左值,不能引用右值。

// 左值引用只能引用左值,不能引用右值。
  int a = 10;
  int& ra1 = a;  // ra为a的别名
  //int& ra2 = 10;  // 编译失败,因为10是右值

2、但是const左值引用既可引用左值,也可引用右值。

// const左值引用既可引用左值,也可引用右值。
  const int& ra3 = 10;
  const int& ra4 = a;

3、意义

1、做函数参数:减少拷贝,提高效率,可做输出型参数。

2、做函数返回值:减少拷贝,提高效率。引用返回,可修改返回对象 。


二、右值与右值引用

1、右值

右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址

以下 10,x+y,fmin(x,y)都是常见的右值 :

double x = 1.1, y = 2.2;
10;
x + y;
fmin(x, y);

2、右值引用

右值引用就是对右值的引用,给右值取别名。

以下几个都是对右值的右值引用:

int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = fmin(x, y);

注:右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可以取到该位置的地址,也就是说例如:不能取字面量10的地址,但是rr1引用后,可以对rr1取地址,也可以修改rr1。如果不想rr1被修改,可以用const int&& rr1 去引用。

总结:

1、右值引用只能引用右值,不能引用左值。

// 右值引用只能右值,不能引用左值。
int&& r1 = 10;
//下面的错误
int a = 10;
int&& r2 = a;

2、但是右值引用可以引用move以后的左值。

// 右值引用可以引用move以后的左值
int&& r3 = std::move(a);

三、右值引用使用场景和意义

C++11为什么要增加右值引用呢?接着往下看。

我们看看 to_string的实现:

string to_string(int value)
  {
    bool flag = true;
    if (value < 0)
    {
      flag = false;
      value = 0 - value;
    }
    hwc::string str;
    while (value > 0)
    {
      int x = value % 10;
      value /= 10;
      str += (x + '0');
    }
    if (flag == false)
    {
      str += '-';
    }
    std::reverse(str.begin(), str.end());
    return str;
  }

上面的代码中,str 是临时变量,出了作用域就会销毁,所以无法使用左值引用传返回值来减少拷贝、提高效率。

如果函数的返回的是一个局部的对象,该对象出了函数作用域就被销毁了,这种情况下就不能用左值引用作为返回值了,只能以传值的方式返回(深拷贝)。这就是左值引用的不足之处。

基于上面的问题,右值引用就能够很好地解决这个问题。

1、右值的分类

内置类型右值 —— 纯右值。

自定义类型右值—— 将亡值。

2、移动构造

移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己

//拷贝构造
string(const string& s)
  :_str(nullptr)
  , _size(0)
  , _capacity(0)
{
  string tmp(s._str);
  swap(tmp);
}
//移动构造
string(string&& s)//右值引用
  :_str(nullptr)
    ,_size(0)
  ,_capacity(0)
{
  swap(s);
}

to_string 没有调用深拷贝的拷贝构造,而是调用了移动构造,移动构造中没有新开空间,拷贝数据,所以效率提高了。

实现了string 的移动构造后,因为 to_string 的返回值是一个右值,是一个将亡值,所以如果 string 既有拷贝构造函数,也有移动构造函数,那么编译器会去调用移动构造,并且右值在出了作用域后会自动销毁。这里就是一个移动语义。

3、移动赋值

//拷贝赋值
string& operator=(const string& s)
{
  string tmp(s);
  swap(tmp);
  return *this;
}
//移动赋值
string& operator=(string&& s)
{
  swap(s);
  return *this;
}

四、万能引用

void Fun(int& x)
{
  cout << "17" << endl;
}
void Fun(int&& x)
{
  cout << "18" << endl;
}
template<typename T>
void fun(T&& t)
{
  Fun(t); //会被折叠
}

如上代码,在模板中的&&不代表右值引用,而是万能引用(引用折叠),其既能接收左值又能接收右值。模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值。

即:t 既能引用左值,也能引用右值。但是都会被折叠为左值引用。


五、完美转发

从四中,我们知道了万能引用会被折叠为左值。但是如果我们本身就想传右值呢?我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用我们下面学习的完美转发。

std::forward 完美转发在传参的过程中保留对象原生类型属性。

如下代码:std::forward<T>(t)在传参的过程中保持了 t 的原生类型属性。

template<typename T>
void PerfectForward(T&& t)
{
    Fun(std::forward<T>(t));
}
目录
相关文章
|
1月前
|
编译器 C语言 C++
从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值(中)
从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值
13 1
从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值(中)
|
5天前
|
编译器 C++ 开发者
C++一分钟之-右值引用与完美转发
【6月更文挑战第25天】C++11引入的右值引用和完美转发增强了资源管理和模板灵活性。右值引用(`&&`)用于绑定临时对象,支持移动语义,减少拷贝。移动构造和赋值允许有效“窃取”资源。完美转发通过`std::forward`保持参数原样传递,适用于通用模板。常见问题包括误解右值引用只能绑定临时对象,误用`std::forward`,忽视`noexcept`和过度使用`std::move`。高效技巧涉及利用右值引用优化容器操作,使用完美转发构造函数和创建通用工厂函数。掌握这些特性能提升代码效率和泛型编程能力。
14 0
|
1月前
|
存储 安全 C语言
从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值(上)
从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值
16 2
|
1月前
|
编译器 C语言 C++
从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值(下)
从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值
22 1
|
1月前
|
编译器 C++ 容器
【C++11(一)】右值引用以及列表初始化
【C++11(一)】右值引用以及列表初始化
|
1月前
|
存储 安全 程序员
C++11:右值引用
C++11:右值引用
18 0
|
1月前
|
存储 算法 程序员
【C++入门到精通】右值引用 | 完美转发 C++11 [ C++入门 ]
【C++入门到精通】右值引用 | 完美转发 C++11 [ C++入门 ]
28 0
|
2天前
|
C++
【C++】日期类Date(详解)②
- `-=`通过复用`+=`实现,`Date operator-(int day)`则通过创建副本并调用`-=`。 - 前置`++`和后置`++`同样使用重载,类似地,前置`--`和后置`--`也复用了`+=`和`-=1`。 - 比较运算符重载如`&gt;`, `==`, `&lt;`, `&lt;=`, `!=`,通常只需实现两个,其他可通过复合逻辑得出。 - `Date`减`Date`返回天数,通过迭代较小日期直到与较大日期相等,记录步数和符号。 ``` 这是236个字符的摘要,符合240字符以内的要求,涵盖了日期类中运算符重载的主要实现。
|
4天前
|
C++
C++职工管理系统(类继承、文件、指针操作、中文乱码解决)
C++职工管理系统(类继承、文件、指针操作、中文乱码解决)
7 0
C++职工管理系统(类继承、文件、指针操作、中文乱码解决)
|
2天前
|
存储 编译器 C++
【C++】类和对象④(再谈构造函数:初始化列表,隐式类型转换,缺省值
C++中的隐式类型转换在变量赋值和函数调用中常见,如`double`转`int`。取引用时,须用`const`以防修改临时变量,如`const int& b = a;`。类可以有隐式单参构造,使`A aa2 = 1;`合法,但`explicit`关键字可阻止这种转换。C++11起,成员变量可设默认值,如`int _b1 = 1;`。博客探讨构造函数、初始化列表及编译器优化,关注更多C++特性。