引转移——避免在通用引用上重载

引转移——避免在通用引用上重载

文章目录

    • 避免在通用引用上重载
      • 最危险的反模式
      • 为什么通用引用重载如此危险?
      • 特殊危险地带:构造函数
      • 何时"安全"?

避免在通用引用上重载

核心要点要点
1在通用引用上重载,几乎总是导致函数被意外调用的频率远超预期
2完美转发构造函数尤其危险:会劫持非 const 左值的拷贝请求,并劫持派生类对基类拷贝/移动构造的调用
3通用引用的"贪婪匹配"特性使其在重载决议中具有压倒性优势
4替代方案见条款27——不要试图在通用引用上重载

最危险的反模式

//全局容器std::multiset<std::string>names;// 版本1:针对左值voidlogAndAdd(conststd::string&name){names.insert(name);}// 版本2:为"高效"处理右值而添加的通用引用重载 ❌template<typenameT>voidlogAndAdd(T&&name){names.insert(std::forward<T>(name));}

看起来完美——直到你真正使用它:

std::string name="Alice";logAndAdd(name);// 调用版本1 还是版本2?// 答案:调用版本2!T 推导为 std::string&// 模板版本是精确匹配,压倒一切// 如果 name 是 const 的,版本1 仍然不匹配——还是版本2shortidx=42;logAndAdd(idx);// ❌ 编译错误!// T 推导为 short,std::multiset<std::string>::insert 期望 std::string// short 本可隐式转换为 std::string(若调用版本1),但模板完美匹配 short// 错误信息埋藏在 std::string 构造函数深处 —— 令人发指!

为什么通用引用重载如此危险?

📌核心矛盾:通用引用模板是 C++ 的"贪婪匹配器"——它能精确匹配几乎任何类型,远比需要类型转换的非模板重载版本更有竞争力。

重载决议优先级: 1. 精确匹配(通用引用 T&&) ← 🏆 被选中! 2. 需要派生类 → 基类转换 3. 需要 const 转换 4. 需要用户定义的隐式转换 ... 99. 需要标准隐式类型转换(const T&) ← 😢 被跳过

特殊危险地带:构造函数

classPerson{public:template<typenameT>explicitPerson(T&&n)// 通用引用构造("贪婪"):name(std::forward<T>(n)){}Person(constPerson&rhs);// 拷贝构造(编译器生成或自定义)private:std::string name;};Personp("Nancy");autocloneOfP(p);// ❌ 编译错误!// 你期望:调用 Person(const Person&) 拷贝构造函数// 实际发生:调用 Person(T&&) 通用引用版本!//// T 推导为 Person& → Person(Person& n)// p 不是 const → 精确匹配 Person& 比 const Person& 更优!
classSpecialPerson:publicPerson{public:SpecialPerson(constSpecialPerson&rhs):Person(rhs)// ❌ 调用 Person(T&&),而非 Person(const Person&)!{}SpecialPerson(SpecialPerson&&rhs):Person(std::move(rhs))// ❌ 同样问题{}};// rhs 和 std::move(rhs) 的类型是 SpecialPerson& 和 SpecialPerson&&// 而非 Person& / Person&& → 模板比基类拷贝/移动更匹配!

何时"安全"?

🔧只有当通用引用是唯一的重载版本,或者模板参数受到足够严格的约束,通用引用重载才可能安全。

// ✅ 安全:唯一版本,没有重载template<typenameT>voidlogAndAdd(T&&name){names.insert(std::forward<T>(name));}