往期精彩:
- Modern C++ 学习笔记——易用性改进篇
- Modern C++ 学习笔记 —— 右值、移动篇
- Modern C++ 学习笔记 —— 智能指针篇
- Modern C++ 学习笔记 —— lambda表达式篇
- Modern C++ 学习笔记 —— C++面向对象编程
- Modern C++ 学习笔记 —— C++函数式编程
Modern C++ 学习笔记 – 右值、移动篇
关键字:右值,移动,万能引用,完美转发
文章目录
引入
先看如下代码:
class Solution {
public:
bool registerMap(const Struct1 &lhs, const Struct2 &rhs)
{
return myMap.insert(make_pair<Struct1, Struct2>(lhs, rhs)).second; // C++ 98
}
private:
map<Struct1, Struct2> myMap;
};
上段代码中对make_pair
的两个模板参数进行了显示定义,不依赖编译器进行模板的型别推导。在 C++98 的环境是可以编译通过的,但是在 C++11 或者更高版本缺无法通过编译,编译器会毫不犹豫得警告你不能这样玩:cannot convert 'lhs' (type 'const Struct1') to type 'Struct1&&'
,具体原因可以通过看 make_pair 的标准库得知。
#if __cplusplus >= 201103L
template<class _T1, class _T2>
constexpr pair<_T1, _T2>
make_pair(_T1&& __x, _T2&& __y)
{
return pair(std::forward<_T1>(__x), std::forward<_T2>(__y));
}
#else
template<class _T1, class _T2>
inline pair<_T1, _T2>
make_pair(_T1 __x, _T2 __y) {
return pair<_T1, _T2>(__x, __y); }
#endif
上述标准库代码实际上进行了删减,但是不影响我们用来理解 C++高版本带来的变化。在 C++11 版本引入右值后,为适应各个型别在标准库中进行了万能引用的改造。在之前的例子中,对 make_pair 指定模板参数后,编译器会将其推导为如下的形式,自然在传入const Struct &
时,编译器就会报错。
constexpr pair<Struct1, Struct2> make_pair(Struct1&& __x, Struct2&& __y)
{
return pair(std::forward<Struct1>(__x), std::forward<Struct2>(__y));
}
右值和移动究竟解决了什么问题?
值类别
C++标准里面规定了下面这些值类别:
- 左值 lvalue是有标识符、可以取地址的表达式,通常有:变量\函数\数据成员的名字,返回左值引用的表达式(++x, x=1),字符串字面量(“hello world”).
字符串字面量之所以是左值,是因为它需要占用主存,是可以取地址的。而整数,字符等可以直接放在寄存器,不能取地址。cout << "hello world"s.size() << endl;
- 右值 rvalue表达式结束后不在存在的历史对象。进一步划分为纯右值和将亡值。
- 纯右值 prvalue是没有标识符、不可取地址的表达式,一般称为“临时对象”,通常有:返回非引用类型的表达式(x++, x+1),除字符串字面量之外的字面量(42、true).
- 将亡值 xvalue值即将被销毁,却能够被移动的值,他有地址但是仅编译器能够操作,程序不可访问,资源可以服用。
int a = 3;
int setValue()
{
a = 4; // 4 是字面量,是纯右值
return a; // 执行结束后 a 成为将亡值
}
int main(void)
{
int b = setValue(); // 右值赋值给左值
int *p = &setValue(); // 纯右值没有地址
1 + 4 = 5; // 右值无法在等号左边
(b > 3) ? i : j = 4; // 此时的三目运算符是左值
return 0;
}
移动
以下代码为智能指针shared_ptr
的部分实现,我们使用右值引用的目的就是实现移动,而实现移动的意义是减少运行的开销。