本篇笔记包含 Term 23~30。
Term 23:理解 std::move 和 std::forward
std::move
实施的是强制转换到右值型别- 仅当传入的实参被绑定到右值时,
std::forward
实施强制转换到右值型别 std::move
和std::forward
在运行期不做任何操作
Term 24:区分万能引用和右值引用
- 如果函数模板形参是
T&&
形式(包括auto&&
),并且 T 依赖于型别推导,则为万能引用 - 如果
T&&
形式不依赖型别推导,则为右值引用 - 如果使用右值初始化万能引用,得到一个右值引用;使用左值初始化万能引用,则得到一个左值引用
Term 25:针对右值引用实施 std::move,针对万能引用实施 std::forward
- 针对右值引用的最后一次实施
std::move
,针对万能引用的最后一次实施std::forward
- 作为按值返回的函数的右值引用和万能引用,依照 1. 中规则
- 如果按值返回局部对象,可能适用于返回值优化,不要使用
std::move
或者std::forward
Term 26:避免依万能引用型别进行重载
万能引用型别的重载函数会精确匹配一些程序员并无意图匹配的类型,应当谨慎使用,完美转发构造函数的问题更多。
Term 27:熟悉依万能引用型别进行重载的替代方案
使用 std::enable_if
对模板施加限制,就可以将万能引用和重载一起使用,不过这种技术控制了编译器可以调用到接受万能引用的重载版本的条件。万能引用在性能方面有优势,但是良好的代码编写令人头疼,一个例子:
class Person {
public:
template <typename T,
typename = std::enable_if_t< // 条件判断
!std::is_base_of<Person, std::decay_t<T>>::value && // 移除修饰词和引用特性,判断是否为 Person 的派生类
!std::is_integral<std::remove_reference_t<T>>::value>> // 移除类型的引用,判断是否为整型
explicit Person(T&& n) : name(std::forward<T>(n)) {} // 接受 std::string 型别以及可以强制转换到 std::string 型别的实参的构造函数
explicit Person(int idx) : name(nameFromIdx(idx)) {} // 接受整形实参的构造函数
// copy 和 move 构造函数等
private:
std::string name;
};
Term 28:理解引用折叠
引用折叠的规则:形参和实参都是引用类型时,只有两者都为右值引用,最终的参数类型才是右值引用,否者为左值引用。引用折叠可能出现在四种情况下:模板实例化、auto
型别生成、使用 typedef
和别名声明出现双重引用、decaltype
。
Term 29:假定移动操作不存在、成本高、未使用
在未知是否支持移动语义的情况下(比如编写模板),使用复制操作
Term 30:熟悉完美转发的失败情形
完美转发失败是由于模板类型不能成功推导,或者结果错误,涉及的情形有:大括号初始物、以 0 或 NULL 表达的空指针、仅有声明的整形 static const
成员变量、模板或者重载的函数名字、位域