移动语义

1. 值分类

每个C++表达式(带操作数的操作符,字面量,变量名,等)被分类为两个独立属性:类型和值类别。每个表达式持有一些非引用类型,且每个表达式明确属于三种基本的值类别之一:prvalue(纯右值),xvalue(亡值),和lvalue(左值),定义如下:

  • glvalue(泛(“generalized”)左值)是一个表达,其求值决定了对象、位域或函数的标识,如变量名,函数类型。

a glvalue (“generalized” lvalue) is an expression whose evaluation determines the identity of an object, bit-field, or function;

  • prvalue(纯右值)是一个表达式,其求值为:
    • 操作符运行所计算出来的值(这样的纯右值没有结果对象),或
    • 初始化一个对象或者一个位域(这样的prvalue被称为有结果对象)。所有的类和数组纯右值都有结果对象即使它被弃置了。 在确切的语境中,临时物化导致创造除一个临时量作为结果对象。
  • xvalue(“eXpiring” value)是一个泛左值,它表示一个对象或位域,其资源可以被重用(右值引用?)。
  • lvalue(这样叫是因为历史原因,因为左值可以出现在赋值表达式的左边),是非亡值的泛左值。
  • rvalue(这样叫是因为历史原因,因为右值可以出现在赋值表达式的右边),是纯右值或者亡值。

基本分类

lvalue

左值:有生命周期,可以使用表达式访问(右值是可由表达式创造)。

下列表达式是左值表达式(lvalue expressions):

  • 变量名,函数名,数据成员。即使变量类型是右值引用,组成它名字的表达依然是一个左值表达;
	char&& char_rref = 'x';
	// char_rref 是一个左值(表达),它标识一个 char&& 类型的对象,其名字为char_rref
  • 一个函数调用或者重载运算符表达式,其返回类型是左值引用。
	std::getline(std::cin, str); 
	std::cout << 1;
	str1 = str2;
	++it;

重点在于其返回值为左值引用,

  • a = b a += b a %= b, 和所有其他的内置类型赋值和复合赋值的表达式;
  • ++a--a 内置自增/自减运算;
  • *p,内置 indirection 表达式;
  • a[n]和p[n],内置下标表达式,where one operand in a[n] is an array lvalue(since c++11);
  • a.m, the member of object expression, except where m is a member enumerator or a non-static member function, or where a is an rvalue and m is a non-static data member of non-reference type;
  • p->m, the built-in member of pointer expression, except where m is a member enumerator or non-static member function.
  • a.*mp, the pointer to member of pointer expression, except where m is a member enumerator or a non-static member function;
  • p->*mp, the built-in pointer to member of pointer expression, where mp is a pointer to data member;
  • 字符串字面量,如"hello world"
  • 左值引用类型的转换, static_cast<int &>(x)
prvalue

下列表达式是右值表达式:

  • 字面量(除字符串表达式),例如42, true
  • 没有结果对象的所有操作
  • 返回临时结果对象的所有操作
xvalue

xvalue(eXpiring value - 过期值) 亡值就本身而言,是一个泛左值,它有自己的名字(expression),标识一个对象或位域。这个对象或位域的资源可以被重用。换句话说,亡值所标示的对象(没有其资源的使用权,只有访问权?)其资源的所有权可以进行转移,被其他对象或位域获得。

The following expressions are xvalue expressions:
*

  • a[n], the build-in subscript expression, where one operand is a array rvalue;

2. 方法

std::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;
}

std::forward

完美转发。

template<typename _Tp>
constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type& __t) noexcept {
	return static_cast<_Tp&&>(__t);
}

template<typename _Tp>
constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type&& __t) noexcept {
	return static_cast<_Tp&&>(__t);
}

完美转发std::forward返回右值引用的右值,作为传参时,只能被绑定为右值引用。
不使用std::forward时,传入参数为右值引用的左值,可以绑定为右值引用或者int等。
在使用变参模板时,不知道参数类型为左值或者是右值的情况下,使用完美转发。

  1. 将左值作为左值或者右值转发,取决于T。
    当 t 是转发引用(forwarding reference,一个函数参数,被声明为右值引用指向未被cv限定符限定的函数模板参数)时,这个重载向另一个具有相同值类型的函数转发参数。

右值引用可以隐式转换为常量左值引用。
右值引用参数接受右值初始化。
使用右值引用初始化右值引用,由于右值引用本身为左值,其指向的值即为左值。std::forward将右值引用的左值性去除了,使其指向的左值变为右值。

std::move

强转为右值引用。

template<typename _Tp>
constexpr typename std::remove_reference<_Tp>::type&&
move(_Tp&& __t) noexcept {
	return static_cast<typename std::remove_reference<_Tp>::type&&>(__t);
}

传入实参隐式转换为右值引用_Tp&&

std::forward和std::move

std::move是无条件的转为右值引用,std::forward是有条件的转为右值引用,更准确的说叫做完美转发(Perfect forwarding),而std::forward里面蕴含这的条件则是引用折叠(Reference Collapsing)。

对于std::move来说,其boost的实现基本上等价于如下形式:

template <typename T>
decltype(auto) std::move(T&& __t) {
	using return_type = std::remove_reference<T>;
	return static_cast<return_type&&>(__t);
}

可以看到,无论__t具有何种类型,他都会被强转为return_type&&。
模板这里的T&&类型,如果传递的__t是一个左值,那么T将会被推断为左值引用(Lvalue Reference),其Param Type也是左值引用。若你传递进来的__t是右值,那么T则是正常的param类型,如int等,其Param Type结果是T&&。

template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

这里,T是需要构造的模板类,Args是T的构造函数所需的参数的模板类型。问题又来了,使用new T(std::forward(args)…)和直接使用new T(args…)的区别是什么?答案是,前者会完美转发args的实际类型(保留其lvalue和rvalue性质),而后者始终按照lvalue看待。结果很明显,如果T的构造函数提供了支持lvalue和rvalue的多个重载,那么使用std::forward的方式会避免额外的内存拷贝。

引用折叠(Reference Collapsing)

C++不允许引用指向引用,然而事实上,我们却会出现Lvalue reference to Rvalue reference[1],Lvalue reference to Lvalue Reference[2], Rvalue reference to Lvalue reference[3], Rvalue reference to Rvalue reference[4]等4种情况,那么针对这样情况,编译器会根据引用折叠规则变为一个Single Reference,只有[4]会转换为右值引用,其他都是左值引用。

std::decay

定义于头文件<type_traits>

tempalate< class T >
struct decay;		// since C++ 11  

向type T 应用 lvalue-to-rvaluearray-to-pointer,和function-to-pointer的隐式转换,移除cv-qualifiers,并将结果的类型定义为member typedef type

Helper types
template< class T >
using deacy_t = typename deacy<T>::type; // since C++ 14
可能的实现
template< class T >
struct deacy {
private:
	typedef typename std::remove_reference<T>::type U;
public:
	typedef typename std::conditional<
		std::is_array<U>::value,
		typename std::remove_extent<U>::type*,
		typename std::condition<
			std::is_function<U>::value,
			typedef std::add_pointer<U>::type,
			typedef std::remove_cv<U>::type
		>::type
	>::type type;
}

std::integer_sequence

定义于头文件

template< class T, T... Ints >
class integer_sequence;		// since C++ 14
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值