当前位置: 首页 > news >正文

Effective Modern C++ 条款26:避免在通用引用上重载

在C++编程中,函数重载是一项强大的特性,它允许我们为不同的参数类型提供不同的实现。然而,当涉及到通用引用(universal references)时,重载可能会带来意想不到的问题。Effective Modern C++的条款26明确指出:避免在通用引用上进行重载。本文将通过一个具体的例子,深入探讨这一条款的重要性,并分析其背后的原因。


示例:logAndAdd函数的重载问题

假设我们需要编写一个函数logAndAdd,它的功能是将一个名字记录到日志中,并将其添加到一个全局的std::multiset<std::string>集合中。为了提高效率,我们考虑使用通用引用和完美转发技术。

初始实现

std::multiset<std::string> names;void logAndAdd(const std::string& name) {auto now = std::chrono::system_clock::now();log(now, "logAndAdd");names.emplace(name);
}

这个实现没有问题,但效率不高。对于右值参数(如临时对象或字符串字面量),它仍然会进行一次拷贝操作。

使用通用引用优化

为了提高效率,我们重写logAndAdd,使用通用引用和完美转发:

template<typename T>
void logAndAdd(T&& name) {auto now = std::chrono::system_clock::now();log(now, "logAndAdd");names.emplace(std::forward<T>(name));
}

这样,右值参数会被移动而不是拷贝,字符串字面量也会直接构造,避免了不必要的临时对象。

支持索引参数的重载

有些情况下,用户可能需要通过索引查找名字。为了支持这种需求,我们为logAndAdd添加了一个重载版本:

void logAndAdd(int idx) {auto now = std::chrono::system_clock::now();log(now, "logAndAdd");names.emplace(nameFromIdx(idx));
}

问题的出现

现在,我们发现当传递一个short类型的索引时,程序会出错:

short nameIdx = 42;
logAndAdd(nameIdx); // 错误!

为什么会出现这个问题呢?让我们仔细分析。


问题分析:重载解析规则

C++的重载解析规则决定了在多个重载函数中选择哪一个函数。规则是:精确匹配优先于类型提升的匹配

在我们的例子中,logAndAdd有两个重载版本:

  1. template<typename T> void logAndAdd(T&& name)
  2. void logAndAdd(int idx)

当传递一个short类型的参数时,会发生以下情况:

  • 通用引用版本:模板参数T会被推导为short,因此函数签名变为void logAndAdd(short&& name)。这是一个精确匹配,因为short类型的参数可以与short&&完美匹配。
  • int版本short类型可以通过类型提升转换为int,因此这个版本也是一个候选函数。

根据重载解析规则,通用引用版本会优先被调用。然而,logAndAdd(short&& name)的实现会尝试将short类型的参数转发给std::multiset<std::string>emplace函数,而std::string没有接受short类型的构造函数,因此编译会失败。


为什么通用引用重载会导致问题?

通用引用(T&&)在C++中是非常“贪婪”的,它几乎可以匹配任何类型的参数。具体来说:

  • 对于左值,T&&会被推导为T&,因此函数会接受左值参数。
  • 对于右值,T&&会保持为右值引用。

这意味着,通用引用版本的函数几乎可以匹配所有类型的参数,而不仅仅是预期的那些。当与非通用引用的重载函数(如int版本)同时存在时,通用引用版本会“吞噬”比预期更多的参数类型,导致意外的行为。


解决方案:避免在通用引用上重载

为了避免上述问题,Effective Modern C++建议避免在通用引用上进行重载。如果必须支持不同的参数类型,可以考虑以下替代方法:

1. 避免重载,使用模板特化

如果需要为特定类型提供不同的实现,可以使用模板特化:

template<typename T>
void logAndAdd(T&& name) {// 通用实现names.emplace(std::forward<T>(name));
}template<>
void logAndAdd<int>(int idx) {// 专门为int类型实现names.emplace(nameFromIdx(idx));
}

这样,int类型的参数会调用特化版本,而其他类型会调用通用版本。

2. 使用SFINAE技术

SFINAE(Substitution Failure Is Not An Error)技术可以有条件地启用函数重载。例如,可以编写一个函数,仅在参数类型为int时有效:

template<typename T>
void logAndAdd(T&& name, std::enable_if_t<!std::is_same_v<T, int>, bool> = true) {names.emplace(std::forward<T>(name));
}void logAndAdd(int idx) {names.emplace(nameFromIdx(idx));
}

这样,当传递int类型的参数时,会优先调用非模板版本;而对于其他类型,会调用模板版本。


总结

在C++编程中,函数重载是一项强大的特性,但与通用引用结合使用时,可能会带来意想不到的问题。通用引用的“贪婪”匹配特性会导致重载解析优先选择通用引用版本,而忽略其他可能更合适的重载函数。

为了避免这类问题,Effective Modern C++建议避免在通用引用上进行重载。如果需要支持不同的参数类型,可以考虑使用模板特化或SFINAE技术来实现更精细的控制。

记住,通用引用的强大之处在于其灵活性,但过度使用或不当使用可能会导致代码难以维护和调试。在实际开发中,审查代码并确保没有不必要的通用引用重载,是编写高效、可靠C++代码的关键。


文章转载自:

http://fSogd90X.ysLLp.cn
http://3nLE3wd9.ysLLp.cn
http://Asy1i6xm.ysLLp.cn
http://Jbbv7qft.ysLLp.cn
http://xMZOKpq7.ysLLp.cn
http://bXFm8cjU.ysLLp.cn
http://DUGxrEgd.ysLLp.cn
http://pbRifpJV.ysLLp.cn
http://2Ze9cMxX.ysLLp.cn
http://qwNiwHBX.ysLLp.cn
http://hKfTcZxd.ysLLp.cn
http://cwYzkY8v.ysLLp.cn
http://v47WqRwR.ysLLp.cn
http://7tKPDAWI.ysLLp.cn
http://kRH3sbvL.ysLLp.cn
http://hRsSAEWu.ysLLp.cn
http://RaRIBqyg.ysLLp.cn
http://XpxTYIdf.ysLLp.cn
http://a8jCyNO1.ysLLp.cn
http://IGiqoWdJ.ysLLp.cn
http://JrfL5mQK.ysLLp.cn
http://wRvJQI3E.ysLLp.cn
http://5L1G4YHf.ysLLp.cn
http://Gu1EIH2B.ysLLp.cn
http://aPQmkjjX.ysLLp.cn
http://Em6Ptbby.ysLLp.cn
http://OZSQAs3g.ysLLp.cn
http://4ba4R4G8.ysLLp.cn
http://Prnd2QNW.ysLLp.cn
http://spiRLFVT.ysLLp.cn
http://www.dtcms.com/a/374344.html

相关文章:

  • Android14 init.rc中on boot阶段操作4
  • PYQT5界面类继承以及软件功能开发小记
  • 【机器学习】吴恩达机器学习笔记
  • UE5 性能优化(1) 模型合并,材质合并
  • Selenium4+Pytest自动化测试框架实战
  • 基于RK3568多网多串(6网+6串+2光)1U/2U机架式服务器在储能与电力的应用
  • 【Python】运动路线记录GPX文件的操作API函数,以及相关GUI界面(支持复制、拼接、数据生成、修改,SRT字幕生成等功能)
  • 西嘎嘎学习 - C++vector容器 - Day 7
  • 第三章:Python基本语法规则详解(二)
  • Next系统总结学习(一)
  • 备考系统分析师-专栏介绍和目录
  • 【rk3229/rk3228a android7.1 LPDDR EMMC EMCP 批量sdk】
  • Kali 自带工具 dirb:Web 路径扫描与 edusrc 挖掘利器
  • 【系统分析师】第2章-基础知识:数学与工程基础(核心总结)
  • 房屋安全鉴定机构评价
  • JAVA:io字符流FileReader和FileWriter基础
  • 从零深入理解嵌入式OTA升级:Bootloader、IAP与升级流程全解析
  • 7.0 热电偶的工作原理
  • GPT(Generative Pre-trained Transformer)模型架构与损失函数介绍
  • 【51单片机】【protues仿真】基于51单片机公交报站系统
  • linux常用命令(2)——系统管理
  • Yarn介绍与HA搭建
  • 记个笔记:Cocos打包安卓使用安卓通信模块
  • 基于Python的云原生TodoList Demo 项目,验证云原生核心特性
  • 2025年- H121-Lc28. 找出字符串中第一个匹配项的下标(数组)--Java版
  • 【底层机制】auto 关键字的底层实现机制
  • 【代码随想录算法训练营——Day6(Day5周日休息)】哈希表——242.有效的字母异位词、349.两个数组的交集、202.快乐数、1.两数之和
  • leedcode 算法刷题第二八天
  • KafKa教程
  • 如何在 Ubuntu 22.04 中安装 Docker 引擎和 Linux 版 Docker Desktop 桌面软件