
万能引用、引用折叠与完美转发总览一条主线串起所有点目标让传进来的原始参数尽可能少经过临时对象直接就地构造到目标里。所有规则都是为这件事服务的。一、两块基石1. 类型 ≠ 值类别一个表达式同时有两个属性表达式类型值类别s1string左值lvaluestring(x)string右值rvalues1左值和string(x)右值类型都是string只是值类别不同。2. 引用折叠标准规定推导结果折叠为T TT TT TT T只要出现一个结果就是左值引用。二、万能引用为什么要再写一个函数模板 U规则*X是万能引用当且仅当X是该函数自己、会被推导的模板参数。写法说明listT::push_back(T)T是类模板参数在liststring实例化时已定死 → 普通右值引用listT::push_back(U)套函数模板U由函数自己推导 → 万能引用推导 折叠效果实参左值s1右值string(...)U推导为stringstringU折叠为stringstring结论类模板参数T在liststring时定死永远变不成万能引用必须让函数自己推导U才能左右值通吃 用forwardU保留值类别。三、ListNode 两种写法为什么 1 次 vs 2 次构造以new Node(literal)为例字面量类型const char[N]写法 A推导templateclass U ListNode(U)字面量原样进来U const char()[N] → _data(forwardU(data)) 直接调 string(const char*) ⇒ 1 次构造写法 B写死ListNode(const T) / ListNode(T)字面量先隐式转成临时 string进门前 ⇒ 构造 1转换 → _data(data) / _data(move(data)) 再拷/移 ⇒ 构造 2拷贝/移动根因写法 B 的形参类型已经是string字面量必须先在门口变成string临时写法 A 的形参是推导出的原始类型字面量原封不动抵达_data的构造函数。差异只在参数类型 ≠ 元素类型时出现若本来传string两种写法都是 1 次拷/移打平。四、push_back vs emplace_back同一份开销的两个层次emplace_back 把insert/ListNode也做成变参转发让原始参数一路直达_data构造templateclass...Argsvoidemplace_back(Args...args){insert(end(),std::forwardArgs(args)...);}templateclass...Argsiteratorinsert(iterator pos,Args...args){...newNode(std::forwardArgs(args)...);...}templateclass...ArgsListNode(Args...args):_next(nullptr),_prev(nullptr),_data(std::forwardArgs(args)...){}对比lt.push_back(...)和lt.emplace_back(...)调用传字面量...传string左值传std::move(s)push_back门口先转临时string2 次构造→ 再拷/移1 次拷贝1 次移动emplace_back原样直达_data1 次构造1 次拷贝1 次移动层层对应层次push_back vs emplace_backListNode(const T) vs ListNode(U)转发层数push_back只转发一层emplace_back变参转发到底写死类型在门口转换推导类型原样进内部效果字面量先变临时对象字面量先变临时对象优势emplace_back少一次构造推导写法少一次构造emplace_back的效率优势 上面第三节那个字面量先变临时的开销被变参转发消除掉了。五、一张总表场景收左值收右值传字面量保留值类别左右值区分T写死普通右值引用✗ 只收右值✓✗ 先转临时2 次构造部分左值直接绑不上U函数模板万能引用✓UT✓UT✓ 原样直达1 次构造✓ 配合forwardUpush_back一层转发✓ 拷贝✓ 移动✗ 先转临时✓emplace_back变参转发到底✓ 拷贝✓ 移动✓ 1 次就地构造✓六、一句话串起来类模板T在实例化时定死T只是普通右值引用且写死的类型会逼实参在门口先造临时对象所以要再写一个函数模板U/Args靠推导 引用折叠得到万能引用再用forward原样把原始参数送到_data的构造函数——这就是ListNode省一次构造、emplace_back比push_back高效的同一套原理。