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)的严格定义如下:
通俗来说,左值是在内存中(非代码段)占有空间的表达式。其余的表达式均为右值。
如:
// 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