C++值的类型
左值(lvalue, left value),赋值符号左边的值,是表达式结束后依然存在的持久对象。
右值(rvalue, right value),是指表达式结束后就不再存在的临时对象。在C++11之后右值又划分为,纯右值、将亡值。
- 纯右值(prvalue, pure rvalue),是纯粹的字面量,或者是求值结果的结果相当于字面量或匿名临时对象。非引用返回的临时变量,运算表达式产生的临时变量、原始字面量、Lambda表达式、容器中的对象、函数返回值都属于纯右值。
// 临时变量
std::vector<int> v = {1, 2, 3};
auto&& pr = std::move(v[1]);
// v[1]是一个临时对象,pr是它的纯右值引用
// 运算表达式产生的临时变量
auto&& pr2 = std::move(42);
// 原始字面量
auto&& pr3 = std::move("Hello");
// Lambda表达式
auto&& pr4 = []{ return std::string("World"); }();
// 容器中的对象
std::vector<std::string> vec;
vec.emplace_back(std::move("C++11"));
// 临时对象通过emplace_back添加到vec中,返回纯右值引用
// 函数返回值
std::string createStr() { return std::string("C++11"); }
auto&& pr5 = createString();
// createStr返回一个临时对象,pr5是它的纯右值引用
- 将亡值(xvalue, expiring value),C++11为了引入右值引用提出,是即将被销毁、却能够被移动的值。允许资源(如动态内存、文件句柄等)使用右值引用,从一个对象转移到另一个对象,避免不必要的复制操作。
std::vector<int> foo(){
std::vector<int> tmp = {1, 2, 3, 4};
return tmp;
}
std::vector<int> v = foo(); // v是左值,foo()返回值是右值
static_cast<std::vector<int> &&>(tmp); // After C++11
foo()函数返回值tmp在即将被销毁之前,会被拷贝给v,如果tmp非常大,将造成额外开销。在C++11之后编译器会将tmp进行隐式右值转换,此时的v会将foo()返回值进行移动。
将亡值就类似于上述的tmp,即能被识别、同时又能被移动的临时值。
引用
C++中的引用分为常量引用与非常量引用,两者通过绑定的对象是否为常量来区分。当引用常量时,意味着一旦绑定了对象,就不能绑定到另一对象,且不能修改绑定对象的内容(常量类型)。
左值引用
- 左值引用必须在声明时就被初始化,绑定到一个已存在的对象,且在其生命周期内只能绑定到一个对象。
- 通常用作函数参数类型,以避免不必要的复制,提高性能。
右值引用
- 右值引用让临时值的生命周期得以延长、只要变量还活着,那么将亡值将继续存活。
- 右值引用只能绑定到临时对象,用于转移资源所有权,本身不支持引用左值,但在C++11之后可以使用std::move方法,将左值参数无条件转换为右值。
- 通常是非常量引用,因为其用于接收一个即将被销毁的临时对象,并转移该资源。
std::string lv_1 = "value";
// 将左值转换为右值
std::string rv_1 = std::move(lv_1);
// 左值常量引用
const std::string& lv_2 = lv_1 + lv_1;
// 右值引用延长临时对象生命周期
std::string&& rv_2 = lv_1 + lv_2;
// 常量引用无法被修改
lv_2 += "Test"; // 非法
// 非常量引用可以修改临时变量
rv_2 += "Test"; // 合法