前面4章慢慢补。。。因为想起来写笔记的时候已经读到第五章了。
说白了,笔记什么的也不是很必要,Scott Meyers和以前一样,在每个item的最后列出了本item的要点,这就是笔记内容啊。。。
第五章如标题,围绕rvalue references(右值引用)以及延伸出来的两个用途move semantics(移动语义),perfect forwarding(完美转发)来组织的。详细深刻的介绍了这些用来打造编译期语言的利器。。。
以下为Item列表:
-
Item 23:?Understand std::move and std::forward.
-
Item 24:?Distinguish universal references from rvalue references.
-
Item 25:?Use std::move on rvalue references, std::forward on universal references.
-
Item 26:?Avoid overloading on universal references.
-
Item 27:?Familiarize yourself with alternatives to overloading on universal references.
-
Item 28:?Understand reference collapsing.
-
Item 29:?Assume that move operations are not present, not cheap, and not used.
-
Item 30:?Familiarize yourself with perfect forwarding failure cases.
看这章的要点在于,这部分是一个编译期的世界。程序跑起来之后这部分内容就都烟消云散了。。。
首先是universal reference。形如:
1.
template<typename T>
void f(T&& m);
或者
2.
template<typename... Args>
void f(Args&&... args);
或者
3.
auto&& a=b;
就是universal reference。如果有一点不符合这个形式的,比如多个const啥的,都不是universal reference。更确切点说,这个形式的参数并且参与到了type deduction中,才是universal reference。
那么universal reference的强大之处呢?在于它可以在type deduction过程中就可以得到类型的左右值信息。比如形式1,如果参数为lvalue int的话,那么T推导的类型为int&, m的类型为int&(int& &&,reference collapse 得到int&);如果参数为rvalue int的话,那么T推导的类型为int,m的类型为int&&。
上面所提到的reference collapse(引用折叠),是一套针对"引用的引用"的规则。规则很简单:如果两个引用中有一个为lvalue reference,那么结果就是lvalue reference;如果两个引用都是rvalue reference,那么结果是rvalue reference。所以int& &&的类型是int&。
而由rvalue reference(右值引用)带来的move semantics就不多说了。但凡谈C++11的都不会不介绍这个。可是我看过这章后的最大感受就是,move semantic更主要应该用于库的构建;日常应用中必须倍加小心。
后面几个item阐述了更晦涩,更实际的部分。比如除了对universal reference做overload还有什么其他的好选择;怎么识别RVO以不至于误用move semantic而使RVO失效;perfect forward无法正常forward的类型。其中perfect forward无法forward braced initializer 还是很好理解的,这本身就是template type deduction失效的地方。
书看完了,可以看代码了。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); }
把universal reference和reference collapse统统用上,move的源码实在不能在简单了。。。例如:
当参数为lvalue int时,_Tp类型为int&, __t的类型为int&,返回类型为int&&。
当参数为rvalue int时,_Tp类型为int,__t类型为int&&,返回类型为int&&。
而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{static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"" substituting _Tp is an lvalue reference type");return static_cast<_Tp&&>(__t);}
代码其实看起来也不难懂,但是有几个问题就不那么好回答了:
1. forward为什么不直接使用universal reference来实现,而是用了lvalue reference和rvalue reference的重载?
2. rvalue reference的版本为什么多了一个static_assert
第二个问题好回答一点,标准n3797的20.2.4里面规定了如果forward<lvalue ref>(rvalue ref)则编译失败。原因可参考:n2951
现在回到第一个问题。首先:
void f(int&& val)
{
//do staff with val.
}
这里面,val是一个lvalue,虽然val的类型是int rvalue reference。
那么就forward来讲,类型_Tp是必须手动指定的类型(比如上一层type deduction得到的类型结果),而不是__t这个表达式的类型。如果是__t这个表达式的类型,还要forward干嘛,直接写就好了。。。所以type deduction在forward中是被禁止的/完全没用的。forward是用于在上下文中传递需要保留的类型信息,而不是像move那样,对__t进行类型推导。