《Effective Modern C++》读书笔记(1)
在运行期, std::move
和std::forward
都不会做任何操作。
1 | void f(Widget&& param); ///< 右值引用 |
T&&
有两种不同的含义
- 右值引用
- 表示既可以是右值引用也可以是左值引用
万能引用会在两个地方现身
1 | template<typename T> |
1 | auto&& var2 = var1; ///< var2是个万能引用 |
而不涉及型别推导&&
就是右值引用
1 | void f(Widget&& param); ///< 不涉及型别推导 |
const
关键字也可以确定const T&&
是右值引用
1 | template<typename T> |
在一个模板中的T&&
也不一定是万能引用, 见下面。
1 | template<class T, class Allocator = allocator<T>> |
因为push_back
是vector
的成员函数, 如果vector
实例存在的话就一定有确定的类型,所以并不存在型别推导。
另外,声明auto&&
都是万能引用。
针对右值引用实施std::move
,针对万能引用实施std::forward
当转发右值引用给其他函数是,应当对其实施向右值的无条件强制型别转换(通过std::move
),因为它们一定绑定到右值,而当转发万能引用时,应当对其实施向右值的有条件强制型别转换(通过std::forward
), 因为它们不一定绑定到右值。
应当避免针对右值引用实施std::forward
。而另一方面,针对万能引用使用std::move
的想法更为糟糕,因为那样做的后果是某些左值会遭到意外改动(例如某些临时变量)。
1 | class Widget { |
1 | Widget makeWidget() { |
RVO
(return value optimization): 编译器若要在一个按值返回的函数里省略对局部对象的复制(或者移动), 则需要满足两个前提条件: 1. 局部对象型别和函数返回值型别相同. 2. 返回的就是局部对象本身。即使实施RVO
的前提条件满足,但编译器选择不执行复制省略的时候,返回对象必须作为右值处理。当RVO
的前提条件允许时,要么发生复制省略,要么std::move
隐式地被实施于返回的局部对象。
- 针对右值引用的最后一次使用实施
std::move
, 针对万能引用的最后一次使用实施std::forward
。 - 作为按值返回的函数的右值引用和万能引用,依上一条所述采取相同行动。
- 若局部对象可能适用于返回值优化,则请勿针对其实施
std::move
或std::forward
1 | template<typename T> |
std::is_integral<>
不够正确是因为如果传给万能引用name
实参是个左值,那么T
就会被推导为左值引用。因为int&
不是int
.
1 | template<typename T> |
完美转发的含义是我们不仅转发对象,还转发其显著特征:型别、左值还是右值,以及是否带有const
和volation
饰词等等。
大括号初始化物
假设f
的声明如下:
1 | void f(const std:vector<int>& v); |
在此情况下,以大括号初始化物调用f
可以通过编译:
1 | f({1,2,3}) |
但如果把同一大括号初始化物的运用,就是一种完美转发失败的情形。编译器采用推导的手法来取得传递给fwd
实参的型别结果,而后它会比较推导型别结果和f
声明的形参型别。完美转发会在下面两个条件中的任何一个成立时失败:
- 编译器无法为一个或多个
fwd
的形参推导出型别结果。编译器无法编译通过。 - 编译器为一个或多个
fwd
的形参推导出了”错误的”型别结果。
1 | template<typename... Ts> |
1 | f(Widget::MinVals); ///< 没问题, 当f(28)处理 |
无法链接的原因是,完美转发,转发的是入参(Widget::MinVals
)的引用,而引用在编译器底层是指针实现的。由于static
变量并没有被分配实际的地址,所以产生了链接错误。
完美转发的失败情形还包括:重载的函数名字和模板名字。
1 | void f(int (*pf)(int)); ///< 一个接受函数指针入参的函数f |
上面在调用函数f
的时候,其中processVal
仅仅只是函数的名字,但编译器知道匹配的是单入参版本的函数。
而使用完美转发时,编译器是无法知道使用的是什么版本。
1 | fwd(processVal); ///< 编译不过 |
最后一种完美转发失败的情形是位域被用作函数实参的时候。
标准中:非const
引用不得绑定到位域。既然没有办法创建指涉到任意比特的指针(C++标准规定,可以指涉的最小实体是单个char),那自然没有办法把引用绑定到任意比特上了。
1 | struct IPV4Header { |
把位域传递给完美转发函数的关键,就是利用转发目的函数接收的总是位域值的副本这一事实。可以自己复制一份,并以该副本调用。
1 | auto length = static_cast<std::uint16_t>(h.totalLength); |