当前位置: 代码迷 >> 综合 >> Effective Modern C++ 笔记 第五章:Rvalue References, Move Semantics, and Perfect Forwarding
  详细解决方案

Effective Modern C++ 笔记 第五章:Rvalue References, Move Semantics, and Perfect Forwarding

热度:95   发布时间:2023-12-21 04:11:05.0

前面4章慢慢补。。。因为想起来写笔记的时候已经读到第五章了。


说白了,笔记什么的也不是很必要,Scott Meyers和以前一样,在每个item的最后列出了本item的要点,这就是笔记内容啊。。。

第五章如标题,围绕rvalue references(右值引用)以及延伸出来的两个用途move semantics(移动语义),perfect forwarding(完美转发)来组织的。详细深刻的介绍了这些用来打造编译期语言的利器。。。

以下为Item列表:


  1. Item 23:?Understand std::move and std::forward.
  2. Item 24:?Distinguish universal references from rvalue references.
  3. Item 25:?Use std::move on rvalue references, std::forward on universal references.
  4. Item 26:?Avoid overloading on universal references.
  5. Item 27:?Familiarize yourself with alternatives to overloading on universal references.
  6. Item 28:?Understand reference collapsing.
  7. Item 29:?Assume that move operations are not present, not cheap, and not used.
  8. 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进行类型推导。

  相关解决方案