[笔记] C++左右值、引用、移动语义

2022/2/23 14:24:45

本文主要是介绍[笔记] C++左右值、引用、移动语义,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

准备

decltype()

decltype可获取一个变量的类型

int a = 5;

cout << typeid(decltype(a)).name() << endl; // "int"
decltype(a) b = 5; // 等价于 int b = 5;

左值、右值

表达式的定义:

An expression is a sequence of operators and operands that specifies a computation. An expression can result in a value and can cause side effects.

左值(lvalue)、右值(rvalue)以及一些更细的概(xvalue,glvalue,prvalue)的严格定义如下:
image

通俗来说,左值是在内存中(非代码段)占有空间的表达式。其余的表达式均为右值。
如:

// i 表示int变量, p表示指针
// 这些是左值
int i;
Foo o();
arr[5];
(p + 3)

{
	int z = 0;
	[&z]() { int& rz = z; return rz; }(); // 可以[&z]() { int& rz = z; return rz; }() = 5
}


//这些是右值
5
Foo(3);
i + 3 // 不能有&(i+3)
int k = []() {return 5; }();

注: 在C语言中,左值定义为可以放在赋值语句左边的表达式,右值则是右边

左值引用、右值引用

左值引用

在C++ 11中新增了右值引用,C++ 11之前的"引用"即现在的左值引用.

可以用is_lvalue_reference<T>()来判断类型T是否为左值引用。

int i = 5;
int& ref_i = i;
cout << is_lvalue_reference<int&>() << endl; // true
cout << is_lvalue_reference<decltype(ref_i)>() << endl; // true

语法糖

const int& i = 5;

等价为

const int five = 5;
const int& i = 5;

这在一些接收引用作为参数的函数中有用

void f(const string& str) {
}

f(string("Hello"));

// 如果没有这个语法糖,需要:
string x = "Hello";
f(x);

右值引用

对于右值,可以用auto&&来引用

int&& i = 5;

任何引用都是左值
引用类型本质上是一级指针

应用:区分左值和右值

通过左值引用和右值引用,可以轻松判断传入的参数是左值还是右值

void f(int& x) {
	cout << "lvalue " << x << endl;
}
void f(int&& x) {
	cout << "rvalue " << x << endl;
}

f(5); // rvalue 5
int i = 5;
f(i); // lvalue 5

应用:拷贝构造函数和移动构造函数

再看一下智能指针的部分原理(Unique实现)

	SimpleUniquePtr& operator=(const SimpleUniquePtr& rh) = delete; // disable copy assignment: this = rh
	SimpleUniquePtr& operator=(SimpleUniquePtr&& rh) noexcept       // move assignment: this = rh
	{
		if (this == &rh)
			return *this;

		delete this->ptr;
		this->ptr = rh.ptr;
		rh.ptr = nullptr; // 防止rh的资源被delete

		return *this;
	}

智能指针在被赋值时,根据被赋的值有两种截然不同的反应: 如果是右值,则夺走对方的指针;如果是左值,则这种行为会引发错误

// 不能这么做!因为智能指针是Unique
	auto str = new char[8] {"Hello"};
	auto ptr1 = SimpleUniquePtr(str);
	auto ptr2 = ptr1; // ptr1是左值
// 可以这么做: 把原来的资源丢掉,绑定到一个新建的资源
	auto str = new char[8] {"Hello"};
	auto str2 = new char[8]{ "World" };
	auto ptr2 = SimpleUniquePtr(str);
	// SimpleUniquePtr(str)在这里被释放
	auto ptr2 = SimpleUniquePtr(str2); // ptr2是右值(xvalue)
	// SimpleUniquePtr(str2)在这里被释放,ptr2留存

引用类型转换:移动语义

move是一个类似于强制类型转换的东西,它可以把左值(和右值)引用转化为右值引用。他本身没有任何内存上的实质作用,更多的和拷贝构造函数和移动构造函数一起使用

他的源码也较为简单,就是移除引用然后再变为右值引用。

constexpr auto&& move(T&& arg)  {
    return static_cast<remove_reference_t<T>&&>(arg);
}
int x = 5;
cout << is_lvalue_reference<decltype(move(x))>() << endl; // false (右值)

例:

	char* str0 = new char[10]{ "Hello" };
	char* str1 = new char[10]{ "World" };
	auto ptr0 = SimpleUniquePtr(str0);
	auto ptr1 = SimpleUniquePtr(str0);

	SimpleUniquePtr ptr2 = ptr1; // 触发operator=(auto&) = delete
	SimpleUniquePtr ptr2 = move(ptr1); // 触发operator=(auto&&), 成功转移所有权
	// ptr1的指针被夺走
	dbg(ptr1.isNull());

引用的引用

左引用的左引用是左引用 (int&)& == int&
右引用的右引用是右引用 (int&&)&& == int&&
左右引用混合是左引用 (int&)&& == (int&&)& = int&

应用:万能引用

template<typename T>
void f(T&& x) {
	cout << is_lvalue_reference<decltype(x)>() << endl;
}

f(3); // false (是右引用) 传入 int&& => (int&&)&& == int&&
int i = 3;
f(i); // true (是左引用) 传入 int& => (int&)&& == int&

参考资料

[1]ISO.Working Draft, Standard for Programming Language C++ N4861.https://isocpp.org/std/the-standard



这篇关于[笔记] C++左右值、引用、移动语义的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程