这部分内容的来源是effective modern c++的第五章。这一章感觉可以作为一个整体去总结,而不是一节一节地写到onenote里去。大概就是把条款里的点提炼一下,用自己的话表述出来。
- 形参一定是左值,即使其型别是右值引用。
- std::move和std::forward做了什么:
- 它们都是函数模版,执行强制类型转换。
- move无条件地把实参强制转换成右值,而不是移动。
- move告诉编译器该对象具备可移动的条件,但不保证经过其转换的对象具备可移动的能力,唯一能保证的是,move后的结果是右值。
- forward在实参是使用右值初始化时,才会执行同样的转换。
- 如果想取得对某个对象执行移动操作的能力,那么就不应将它声明为const。
- T&&:
- T&&会在两种含义取其一:右值引用和万能引用。
- 万能引用是在左值引用和右值引用中择其一的引用,绑定的灵活性强大。
- 判定一个引用是不是万能引用,看两个条件:形式是否严格符合T&&,是否存在类型推导。
- 万能引用强大的绑定特性,让它与重载配合使用时,会出现各种问题,场景有万能引用和普通类型竞争匹配;万能引用成为构造函数参数,甚至会和复制构造函数、移动构造函数进行调用的竞争并胜出。
- 解决方案有:放弃重载;传递const T&类别的形参至函数(编译器自动生成复制构造函数的参数列表,模版非模版函数同时匹配时的优先级问题);标签分派(is_[type]<T>(),remove_reference_t<T>,false/true_type);限制接受万能引用的模版(enable_if_t<condition>::type,!is_same<t1, t2>::value,decay_t<T>::type(去除cv饰词和引用),is_base_of<t1, t2>::value)
- 引用折叠:对模版实例化、auto推导、decltype、typedef/using用于类别声明时,如果出现“引用的引用”,则只有两个引用都是右值引用时,则结果简化为右值引用。否则都是简化为左值引用。
- 万能引用的本质是满足两个条件的右值引用:推导过程区分左值和右值;涉及引用折叠。
- 当转发右值引用给其他函数时,则应该使用move,因为它们一定绑定到右值。如果转发万能引用时,则应该使用forward,因为万能引用不一定是绑定到右值。
- 如果想在单一函数内将某一对象不止一次绑定到右值引用或万能引用,又不想其值被移走,则仅在最后一次使用引用时,对其实施move(右值引用)或forward(万能引用)。很少数的情况下,需要使用move_if_noexcept。
- 对按值返回的函数,如果返回的是一个已经绑定到右值引用或万能引用的函数,则应对应地使用move或forward。
- RVO的条件:局部对象类型和函数返回值类型相同;返回的值就是局部对象本身。,所以对函数内局部对象返回时手动调用move,通常效率上并不优越。
- RVO的前提条件满足,且编译期不执行RVO时,则返回对象必须作为右值处理。
- 移动语义不会带来明显好处的地方:
- 没有提供移动操作,此时移动等价于复制。
- 移动未能比复制操作更快,比如std::array。
- 移动不可用:要求移动操作不可抛出异常,但移动操作未加上noexcept声明。
- 原对象是左值。
- 完美转发的失败情形:
- 无法为包装函数形参本身推导出结果
- 推导出结果的实例化无法通过编译
- 推导出的结果和直接传参调用被包装的函数行为不一致
- 实参种类有:
- 大括号初始化物
- 不用nullptr表示的空指针
- 仅有声明的static const成员变量
- 模板或重载的函数名
- 位域