C++11——3.17-3.20 右值引用

发布时间 2023-09-04 14:19:38作者: 我会变强的

★★★原文链接★★★:https://subingwen.cn/cpp/rvalue-reference/

  • 3.17. 右值和右值引用
  • 3.18. 右值引用的作用以及使用
  • 3.19. 未定引用类型的推导
  • 3.20. 右值引用的传递
  • 3.17. 右值和右值引用

左值,lvalue,locator value,(locator:定位器)

右值,rvalue,read value,(read:只读)

右值分为 纯右值将亡值

右值语法:

int&& a = 10;两个&

能取地址是左值,不能取地址是右值

不能用左值去初始化右值引用,要用右值初始化右值引用

// 左值
int num = 9;	// num是左值
// 左值引用
int& a = num;	// a是左值引用,不占用额外的内存地址,是num的别名
// 右值
// 常量整数,常量字符串等...55, abcd,不能取地址,只能读它的内容
// 右值引用
int&& b = 8;	// ★★★b是右值引用,需要用右值初始化(8)
// int&& b2 = b;	// ★★★报错,不能用 右值引用 初始化 右值引用
// 常量左值引用
const int& c = num;
const int& c2 = b;	// 可以用 右值引用 初始化 常量左值引用
const int& c3 = 1;
const int& c4 = a;
// 常量右值引用
const int&& d = 6;	// 常量右值引用 的初始化也需要用右值(6)

// 常量左值引用 可以使用 常量右值引用 来初始化
const int& e = d;

总结:

常量左值引用 可以用 左值、左值引用、右值、右值引用、常量右值引用 来进行初始化。

 

  • 3.18. 右值引用的作用以及使用

先来看一个例子:

#include <iostream>
using namespace std;

class Test {
public:
	// 默认构造函数
	Test() :m_num(new int(100))
	{
		cout << "construct: my name is jerry" << endl;
		cout << "m_num地址:" << &m_num << endl;
	}
	// 拷贝构造函数
	Test(const Test& a) :m_num(new int(*a.m_num)) 
	{
		cout << "copy construct: my name is tom" << endl;
	}
	// 析构
	~Test() {
		cout << "destruct Test class..." << endl;
		delete m_num;
	}

	int* m_num;
};

Test getObj() {
	Test t;
	return t;
}

int main() {
	Test a = getObj();
	return 0;
}

res:

  

可以看到 a 调用 getObj() 时,先发生了一次默认构造,此时构造的是 getObj() 函数中的 t;然后发生了一次拷贝构造,此时 t 拷贝给 a;getObj() 函数调用完成后析构掉 t,主函数运行结束析构掉 a.

 

给上面的代码加一个 移动构造函数(在 class 内加):

(右值引用构造函数 也称为 移动构造函数)

	// 移动构造函数 作用:★★★复用其他对象中的资源(具体是指其他对象中的 ★★★堆内存)
	// m_num,转移堆内存资源,浅拷贝
	Test(Test&& a):m_num(a.m_num)
	{
		a.m_num = nullptr;	// ★★★因为此对象的 m_num 中存放的地址和 a 对象的 m_num 存放的地址相同,所以 a 对象被析构时 不能让 a.m_num 指向的地址的内存被释放,所以把 a.m_num 置空
		cout << "move construct..." << endl;
	}

res:

  

可以看到 a 调用 getObj() 时,发生了一次默认构造,一次移动构造;

如果类中有移动构造函数,在进行赋值操作时,编译器会判断 = 右边的对象是不是临时对象:

= 右边是临时对象(将亡值):调用移动构造函数;= 右边不是临时对象,调用拷贝构造函数;如果没有移动构造函数,调用拷贝构造函数。

调用 移动构造时,没有进行堆内存资源的申请(拷贝构造函数 有堆内存资源的申请,深拷贝),移动构造函数 实际上进行了一个浅拷贝,实现了 堆内存资源的转移,提高了效率。

 

  • 3.19. 未定引用类型的推导

记住两点即可:

  • 通过右值推导 T&& 或者 auto&& 得到的是一个右值引用类型
    通过非右值(右值引用、左值、左值引用、常量右值引用、常量左值引用)推导 T&& 或者 auto&& 得到的是一个左值引用类型
  • const T&& 表示一个右值引用,不是未定引用类型。

 

  • 3.20 右值引用的传递

看一段代码:

#include <iostream>
using namespace std;

void printValue(int &i)
{
    cout << "l-value: " << i << endl;
}

void printValue(int &&i)
{
    cout << "r-value: " << i << endl;
}

void forward(int &&k)
{
    printValue(k);
}

int main()
{
    int i = 520;
    printValue(i);
    printValue(1314);
    forward(250);

    return 0;
}

res:

  

编译器会根据传入的参数的类型(左值还是右值)调用对应的重置函数(printValue),函数forward()接收的是一个右值,但是在这个函数中调用函数printValue()时,参数k变成了一个命名对象,编译器会将其当做左值来处理

总结:

  • 右值引用类型可能是左值也可能是右值
  • 编译器会将已命名的右值引用视为左值,将未命名的右值引用视为右值

 

★★★原文链接★★★:https://subingwen.cn/cpp/rvalue-reference/

(〃>_<;〃)(〃>_<;〃)(〃>_<;〃)