C++里通常分为左右值,但标准里更细化
这里需要明确右值的概念:
纯右值 prvalue 是没有标识符、不可以取地址的表达式,一般也称之为“临时对象”。
最常见的情况有:
- 返回非引用类型的表达式,如 x++、x + 1、make_shared(42)
- 除字符串字面量之外的字面量,如 42、true
C++11 开始,C++ 语言里多了一种引用类型—右值引用。右值引用的形式是 T&&,比左值引用多一个 & 符号。
我们使用右值引用的目的是实现移动,而实现移动的意义是减少运行的开销。在使用容器类的情况下,移动更有意义。
以下是MSVC的std::move 源代码:
template <class _Ty>
_NODISCARD constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) noexcept { // forward _Arg as movable
return static_cast<remove_reference_t<_Ty>&&>(_Arg);
}
template <class _Ty>
using remove_reference_t = typename remove_reference<_Ty>::type;
template <class _Ty>
struct remove_reference {
using type = _Ty;
using _Const_thru_ref_type = const _Ty;
};
template <class _Ty>
struct remove_reference<_Ty&> {
using type = _Ty;
using _Const_thru_ref_type = const _Ty&;
};
template <class _Ty>
struct remove_reference<_Ty&&> {
using type = _Ty;
using _Const_thru_ref_type = const _Ty&&;
};
MinGW源代码:
template<typename _Tp>
_GLIBCXX_NODISCARD
constexpr typename std::remove_reference<_Tp>::type&&
move(_Tp&& __t) noexcept
{ return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }
/// remove_reference
template<typename _Tp>
struct remove_reference
{ typedef _Tp type; };
template<typename _Tp>
struct remove_reference<_Tp&>
{ typedef _Tp type; };
template<typename _Tp>
struct remove_reference<_Tp&&>
{ typedef _Tp type; };
为了初学者更方便阅读,简化版:
template<typename _Tp>
typename std::remove_reference<_Tp>::type&&
move(_Tp&& __t)
{ return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }
/// remove_reference
template<typename _Tp>
struct remove_reference
{ typedef _Tp type; };
template<typename _Tp>
struct remove_reference<_Tp&>
{ typedef _Tp type; };
template<typename _Tp>
struct remove_reference<_Tp&&>
{ typedef _Tp type; };
这里会出现一个概念,引用坍缩(又称“引用折叠”)。
- 对于 template foo(T&&) 这样的代码,如果传递过去的参数是左值,T 的推导结果是左值引用;
- 如果传递过去的参数是右值,T 的推导结果是参数的类型本身。如果 T 是左值引用,那 T&& 的结果仍然是左值引用——即 type&,也就是&& 坍缩成了 type&。
- 如果 T 是一个实际类型,那 T&& 的结果自然就是一个右值引用。
std::forward 和 std::move 一样都是利用引用坍缩机制来实现。它可以实现目标的参数类型不知道,但我们仍然需要能够保持参数的值类别:左值的仍然是左值,右值的仍然是右值。
写个例子看看现象:
class MoveClass {
public:
MoveClass()
{
cout << "default constructor" << endl;
}
//拷贝构造
MoveClass(const MoveClass& m)
{
cout << "copy constructor" << endl;
}
//移动构造
MoveClass(MoveClass&& m)
{
cout << "move constructor" << endl;
}
MoveClass Get()
{
MoveClass tmp;
//简单返回对象,一般有 NRVO
return tmp;
}
MoveClass GetMove()
{
MoveClass tmp;
//此时使用move 会禁止 NRVO。也就是,用了 std::move 反而妨碍了返回值优化。
return std::move(tmp);
}
};
//main.cpp
int main()
{
MoveClass m;
std::cout << "1-------------------" << std::endl;
MoveClass m1(std::move(m));
std::cout << "2-------------------" << std::endl;
MoveClass m2(std::move(MoveClass()));
std::cout << "3-------------------" << std::endl;
auto r = m.Get();
std::cout << "4-------------------" << std::endl;
auto r1 = m.GetMove();
return 0;
}
输出:
default constructor
1-------------------
move constructor
2-------------------
default constructor
move constructor
3-------------------
default constructor
4-------------------
default constructor
move constructor
m1如果是m1(m) 那么会调用拷贝构造,但是这里用了std::move(m) 调用了移动构造。
m2同理,先默认构造出一个临时对象,再移动构造。
r,r1解释如下:
在 C++11 之前,返回一个本地对象意味着这个对象会被拷贝,除非编译器发现可以做返回值 优化(named return value optimization,或 NRVO),能把对象直接构造到调用者的栈上。
从 C++11 开始,返回值优化仍可以发生,但在没有返回值优化的情况下,编译器将试图 把本地对象移动出去,而不是拷贝出去。这一行为不需要程序员手工用 std::move 进行干预 ——使用 std::move 对于移动行为没有帮助,反而会影响返回值优化。
//TODO:添加std::forward
值类别
The deal with C++14 xvalues 《现代 C++ 编程实战》 吴咏炜
|