深入理解 C++ 函数重载
在 C++ 中, 函数重载是一个非常强大的特性, 允许多个函数使用相同的名称, 但具有不同的参数类型. 重载解析决定了在给定的调用中, 编译器应选择哪个版本的重载函数. 本文将深入探讨 C++ 重载解析的工作原理, 帮助你在实际编程中更好地理解这一机制.
重载(Overload) vs 重写(Override)
多态(polymorphism)的定义
多态性是指一个实体能够表现为多种形式, 并存在两种或两种以上的可能性. 这种特性使得对象能够根据上下文以不同的方式运行.
- 编译时多态性: 编译时多态性主要通过函数, 方法或构造函数的重载实现. 这种重载机制允许程序根据数据类型选择合适的函数调用, 从而实现灵活性和代码的可重用性.
- 运行时多态性: 运行时多态性通过方法重写(Override)实现. 程序在运行时根据对象的实际类型调用正确的方法, 从而支持动态行为.
什么是函数重载?
重载是一种允许为具有不同签名(Signature)的多个声明分配相同名称的机制. 常见的有函数重载和运算符重载.
运算符重载
运算符重载允许为特定运算符定义多种操作. 运算符+
可以用于添加两个数字或连接两个字符串. 这种重载通过重载解析过程得以实现.
int a = 1, b = 2;
std::string sa = "1", sb = "2";int c = a + b; // 3
std::string sc = sa + sb; // "12"
什么是方法重写
-
方法重写通常与继承一起使用. 当子类的方法与父类的方法具有相同名称时, 子类的方法将覆盖父类的方法. 例如在
Animal
和Dog
类中都声明了voice
的方法#include <iostream> class Animal { public:virtual void voice() = 0; };class Dog : public Animal { public:void voice() override { std::cout << "wang wang\n"; } };
-
需要注意, 方法重写与本文讨论的重载解析无关.
为什么需要重载解析
当一个函数调用在编译时包含多个候选函数时, 编译器需要通过重载解析机制来选择最匹配的版本. 重载解析不仅考虑函数的名称, 还需要匹配传递给函数的参数类型, 以确保调用的正确性.
这种机制避免了使用冗长的函数名称, 例如 funStr()
或 funInt()
, 从而提高了代码的可读性和简洁性.
fun("mountain");
fun(17);
重载声明(Declaring Overloads)
重载适用于自由函数, 类(class)的方法和构造函数. 当这些方法满足如下条件时被称为重载:
- 具有完全相同的名称
- 从同一范围可见
- 具有一组不同的参数类型
需要注意的是声明的顺序没有影响.
代码样例
// 重载函数 1
void fun(int) {}// 重载函数 2
void fun(int, double) {}int main() {fun(42); // 调用第一个重载fun(42, 3.14); // 调用第二个重载
}
什么是重载解析(Overload Resolution)
重载解析是选择最合适重载的过程. 编译器在编译时决定调用哪个重载,
- 仅考虑(传递的)参数类型以及它们如何与(接收的)参数类型匹配, 从不考虑实际值
- 如果编译器无法选择一个特定的重载, 则函数调用被视为不明确(ambiguous), 导致编译失败
模板函数或方法参与重载解析过程, 如果两个重载被视为相等, 则非模板函数将始终优先于模板函数.
哪些情况不能重载
-
两个函数仅在返回类型上有所不同. 由于使用返回值是可选的, 因此编译器将其视为定义同一函数两次.
int add(int a, int b) {} double add(int a, int b) {}
-
两个函数仅在其默认参数上有所不同. 默认值不会使函数签名不同.
void fun(int a, int b = 10); void fun(int a, int b = 5); void fun(int a = 1, int b = 10);
-
两个具有相同签名的方法, 其中一个被标记为"静态(
static
)"void fun() {} static void fun() {}
重载解析过程概述
重载解析由编译器计算, 在通常情况下, 此过程会导致调用预期的重载. 但是如果存在如下的情况就会变得很复杂, 并且如果编译器选择了错误的重载函数, 这对用户来说就比较难觉察到这一点.
- 数据类型转换可能很混乱
- 指针/引用数据类型可能无法按预期解析
- 模板函数可以以意想不到的方式推断参数
何时使用重载而不是使用模板
- 应该编写重载集还是单个模板?
- 当不同的类型需要不同的操作过程, 首选重载函数
- 例如:
std::string
的构造函数(const char *)
,(std::string &&)
,(size_type, char)
等
- 例如:
- 当函数主体对所有数据类型执行相同的操作时, 模板是正确的选择.
- 例如: 对于
std::sort(data.begin(), data.end())
来说,data
可以是任何类型, 例如int
,double
,std::string
等
- 例如: 对于
重载解析开始前
- 编译器必须首先运行一个名为名称查找的过程
- 名称查找是查找当前范围内可见的每个函数声明的过程
- 名称查找可能需要参数相关查找
- 模板函数可能需要模板参数推导
- 可见函数声明的完整列表称为重载集
重载解析细节
- 第一步, 将整个重载集放入候选列表中
- 第二步, 删除所有无效候选, 根据 C++ 标准, 无效重载被称为"not viable"
什么因素导致候选函数不可行或无效
-
传递的参数数量与声明不匹配
- 传递太多参数始终被视为无效
- 传递较少参数无效, 除非函数声明中存在默认参数
void doThing(); // candidate A void doThing(int, bool = true); // candidate B doThing(38);
-
传递参数的数据类型无法转换为与声明相匹配, 即使考虑隐式转换.
void doThing(); // candidate A void doThing(int); // candidate B void doThing(std::string); // candidate C doThing(38);
查找最佳重载的过程
- 创建候选列表
- 删除无效重载
- 对剩余候选进行排序
- 如果候选列表中恰好有一个函数的排名高于所有其他函数, 则它将赢得重载解析过程
- 如果最高排名并列, 则使用决胜局
#include <iostream>
#include <string>// candidate A
int lookUp(const std::string* key) { return 'A'; }// candidate B
int lookUp(std::string* key) { return 'B'; }int main() {std::string* str = new std::string("text");int value = lookUp(str);std::cout << value << std::endl; // 调用哪个?
}
答案:
str
的类型为std::string*
, 所以选择B
candidate B
测试题
#include <iostream>
// overload A1
void fun(char value) { std::cout << "A1" << std::endl; }// overload A2
void fun(long value) { std::cout << "A2" << std::endl; }int main() {fun(42); // 调用哪个?
}
答案:
ambiguous (编译失败)
选择候选函数
决胜局是重载解析的最后一步, 用于确定哪个候选函数更匹配
- 当模板和非模板候选函数并列第一时, 将选择非模板函数
- 需要较少"步骤"的隐式转换比需要更多"步骤"的候选函数更匹配
- 如果没有最佳匹配或存在无法解析的平局, 则生成编译器错误
C++20 新增的决胜局
- C++20 引入了与模板一起使用的概念(Concepts), 用于对
T
添加约束, 从而限制允许的类型集 - 对模板施加约束不会改变模板在重载解析方面的含义
- 如果传递的参数满足多个重载模板的概念, 则选择约束更严格的模板
- 此规则仅用作决胜局
测试题
#include <iostream>// overload B1
void fun(char value) { std::cout << "B1" << std::endl; }// overload B2
template <typename T>
void fun(T value) {std::cout << "B2" << std::endl;
}int main() {fun(42); // 调用哪个?
}
答案
B2
当候选集没有最佳匹配时
如何解决模糊函数调用
- 添加或删除重载
- 将构造函数标记为显式以防止隐式转换
- 可以通过
SFINAE
消除模板函数, 无法实例化的模板函数将不会放置在候选集中 - 使用显式转换在调用之前转换参数, 比如
static_cast<>
传递的参数 - 显式构造对象, 比如使用
std::string("some text")
而不是传递字符串文字
当没有最佳匹配时, 编译会生成错误消息 - “没有匹配的函数可供调用”, 并列出可能的候选者, 即使没有可行的候选者
void fun(char value) {}int main() { fun('x', nullptr); }
当最佳匹配不是您想要的
- 重载解析调试起来可能很复杂, 因为没有方法来询问编译器为什么选择特定的重载
- 如果编译器提供详细模式会很有帮助
- 通过故意将不明确的重载添加到候选列表中, 生成的错误消息可能有助于解释原因
- 尝试更改某些传递参数的数据类型
做一些测试
下面的测试有些存在ambiguous
的情况.
Question 1
对比着看下面的两个例子. 思考一下答案.
#include <iostream>// overload C1
void fun(double, int, int) { std::cout << "C1" << std::endl; }// overload C2
void fun(int, double, double) { std::cout << "C2" << std::endl; }int main() {fun(4, 5, 6); // 调用哪个?
}
#include <iostream>
// overload D1
void fun(int, int, double) { std::cout << "D1" << std::endl; }// overload D2
void fun(int, double, double) { std::cout << "D2" << std::endl; }int main() {fun(4, 5, 6); // 调用哪个?
}
答案:
C1/C2 ambiguous(编译错误)
D1
为什么会是这种情况? 从参数匹配上来讲, 1
的情况是有两个参数类型 int
匹配, 2
只有一个参数类型匹配, 似乎是应该选C1
和 D1
, 但是实际情况则并非如此.
在对候选项进行排序的时候, 编译器会这样做
C
的情况:- 检查参数
4
:C1
不匹配,C2
匹配,C2
胜一局, 此时排序为 [C2
,C1
] - 检查参数
5
:C1
匹配,C2
不匹配,C1
胜一局, 此时造成排序顺序不确定, 产生ambiguous
- 检查参数
D
的情况:- 检查参数
4
: 两个都匹配, 并列第一, - 检查参数
5
:D1
匹配,D2
不匹配, 排序为[D1
,D2
] - 检查参数
6
: 均不匹配, 排序不变. - 没有更多的参数了, 当前排序为[
D1
,D2
], 所以D1
获胜, 被选择.
- 检查参数
Question 3
#include <iostream>
// overload E1
void fun(int &) { std::cout << "E1" << std::endl; }// overload E2
void fun(int) { std::cout << "E2" << std::endl; }int main() {int x = 42;fun(x); // 调用哪个?
}
答案:
ambiguous(编译错误)
Question 4
#include <iostream>
// overload F1
void fun(int &) { std::cout << "F1" << std::endl; }// overload F2
void fun(int) { std::cout << "F2" << std::endl; }int main() {fun(42); // 调用哪个?
}
答案:
F2
Question 5
#include <iostream>
// overload G1
void fun(int &) { std::cout << "G1" << std::endl; }// overload G2
void fun(int &&) { std::cout << "G2" << std::endl; }int main() {int x = 42;fun(x); // 调用哪个?
}
答案:
G1
Question 6
#include <iostream>
// overload H1
void fun(int &) { std::cout << "H1" << std::endl; }// overload H2
void fun(int &&) { std::cout << "H2" << std::endl; }int main() {fun(42); // 调用哪个?
}
答案:
H2
总结
重载解析是 C++ 中一个非常强大但复杂的特性. 理解重载解析的细节, 尤其是在处理模棱两可的错误时, 将帮助你写出更高效, 可维护的代码. 在实际编程中, 尽量避免模板和非模板函数的重载冲突, 使用显式转换来消除模糊匹配的情况.
参考链接
- Back To Basics: Overload Resolution - CppCon 2021
相关文章:
深入理解 C++ 函数重载
在 C 中, 函数重载是一个非常强大的特性, 允许多个函数使用相同的名称, 但具有不同的参数类型. 重载解析决定了在给定的调用中, 编译器应选择哪个版本的重载函数. 本文将深入探讨 C 重载解析的工作原理, 帮助你在实际编程中更好地理解这一机制. 重载(Overload) vs 重写(Overri…...

相机和激光雷达的外参标定 - 无标定板版本
1. 实现的效果 通过本软件实现求解相机和LiDAR的外参,即2个传感器之间的三维平移[x, y, z]和三维旋转[roll, pitch, yaw]。完成标定后,可将点云投影到图像,效果图如下: 本软件的优势:(1)无需特…...
Redis 知识速览
文章目录 1. Redis 简介2. Redis 优缺点3. Redis 高性能4. Redis VM 机制5. Redis 数据类型6. 应用场景7. 持久化8. 过期策略9. 内存相关10. 线程模型11. 事务12. 集群 1. Redis 简介 定义:Redis 是一个用 C 语言编写的高性能非关系型(NoSQL)…...

LeetCode 热题 100_从前序与中序遍历序列构造二叉树(47_105_中等_C++)(二叉树;递归)
LeetCode 热题 100_从前序与中序遍历序列构造二叉树(47_105) 题目描述:输入输出样例:题解:解题思路:思路一(递归): 代码实现代码实现(思路一(递归…...
使用sqlplus的easy connect时如何指定是链接到shared server还是dedicated process
在oracle配置了shared server的情况下 可以使用 :shared来指定链接到shared server也可以默认不指定 不指定的情况下会默认链接到shared server 如果想链接到 dedicated process 则必须显式指定链接到dedicated process server type的类型包括DEDICATED, SHARED, or POOLED. […...

ubuntu22.4 ROS2 安装gazebo(环境变量配置)
ubuntu版本:ubuntu22.4 最近在学习ROS2 视频教程古月居的入门课: 视频教程 文字笔记 问题 在学到关于Gazebo的时候,遇到下面问题: 运行 $ ros2 launch gazebo_ros gazebo.launch.py在这里卡住,不弹出gazebo 解决…...
【机器学习:十四、TensorFlow与PyTorch的对比分析】
1. 发展背景与社区支持 1.1 TensorFlow的背景与发展 TensorFlow是Google于2015年发布的开源深度学习框架,基于其前身DistBelief系统。作为Google大规模深度学习研究成果的延续,TensorFlow从一开始就定位为生产级框架,强调跨平台部署能力和性…...

[C++]类与对象(上)
目录 💕1.C中结构体的优化 💕2.类的定义 💕3.类与结构体的不同点 💕4.访问限定符(public,private,protected) 💕5.类域 💕6.类的实例化 💕7.类的字节大小 💕8.类的字节大小特例…...
大数据技术实训:Zookeeper集群配置
一、本地模式安装部署 1)安装前准备 (1)安装jdk (2)拷贝Zookeeper安装包到Linux系统下 (3)解压到指定目录 tar -zxvf zookeeper-3.5.7.tar.gz -C /opt/module/ 2)配置修改 &am…...
HTML5 加载动画(Loading Animation)
加载动画(Loading Animation)详解 概述 加载动画是指在数据加载过程中,向用户展示的一种视觉效果,旨在提升用户体验,告知用户系统正在处理请求。它可以减少用户的等待焦虑感,提高界面的互动性。 常见的加…...

C语言进阶-2指针(一)
目录 1. 字符指针1.1 一般用法:字符指针指向单字符1.2 第二种用法,字符串首地址给指针变量1.3 习题,下面代码的输出结果是什么?为什么? 2. 指针数组2.1实例—— 字符指针数组2.2实例——整形指针数组2.3 例子,识别下下…...
【人工智能】用Python进行对象检测:从OpenCV到YOLO的全面指南
对象检测是计算机视觉领域的核心任务之一,广泛应用于视频监控、自动驾驶、智能安防等多个场景。随着深度学习技术的发展,基于传统方法的对象检测逐渐被基于神经网络的先进模型所取代。本文将系统地介绍如何使用Python进行对象检测,重点探讨了…...

《深度剖析算法优化:提升效率与精度的秘诀》
想象一下,你面前有一堆杂乱无章的数据,你需要从中找到特定的信息,或者按照一定的规则对这些数据进行排序。又或者,你要为一个物流公司规划最佳的配送路线,以降低成本和提高效率。这些问题看似复杂,但都可以…...

Mysql--重点篇--索引(索引分类,Hash和B-tree索引,聚簇和非聚簇索引,回表查询,覆盖索引,索引工作原理,索引失效,索引创建原则等)
索引是数据库中用于加速查询操作的重要机制。通过索引,MySQL可以快速定位到满足查询条件的数据行,而不需要扫描整个表。合理的索引设计可以显著提高查询性能,但不合理的索引可能会导致性能下降和磁盘空间浪费。因此,理解索引的工作…...
matlab使用 BP 神经网络进行数据预测的完整流程,包括数据读取、数据预处理等等
%% 初始化程序 warning off % 关闭报警信息 close all % 关闭所有图窗 clear % 清空变量 clc % 清空命令行 setdemorandstream(172) %设置随机种子为1%% 读取数据 data xlsread(Y.xlsx); %% 划分训练集…...

systemd-networkd NetworkManager 介绍
systemd-networkd 和 NetworkManager 的详细介绍 systemd-networkd 和 NetworkManager 都是 Linux 系统中常用的网络管理工具,但它们的设计目标和使用场景不同。以下是它们的详细介绍、功能、使用场景和差异。 1. systemd-networkd systemd-networkd 是一个由 syst…...

本地部署项目管理工具 Leantime 并实现外部访问
Leantime 是一款开源 AI 项目。它可以在本地直接运行大语言模型 LLM、生成图像、音频等。直接降低了用户使用AI的门褴。本文将详细的介绍如何利用 Docker 在本地部署 Leantime 并结合路由侠实现外网访问本地部署的 Leantime 。 第一步,本地部署安装 Leantime 1&am…...
PHP cURL 函数初学者完全指南
文章精选推荐 1 JetBrains Ai assistant 编程工具让你的工作效率翻倍 2 Extra Icons:JetBrains IDE的图标增强神器 3 IDEA插件推荐-SequenceDiagram,自动生成时序图 4 BashSupport Pro 这个ides插件主要是用来干嘛的 ? 5 IDEA必装的插件&…...

C#中的Array数组,List集合和ArrayList集合--07
目录 一.Array数组概念的简单理解 1.数组的初始化 2.数组的长度 3.数组的克隆和复制 4.数组的清空 5.数组的查找 6.数组的逆转 7.数组的拓展和缩减 8.数组的比较 9.数组的合并 10.使用Array类中的静态方法,如Array.Sort,Array.BinarySearch 等 二.Array数组进阶 1.二…...

基于深度学习的视觉检测小项目(十三) 资源文件的生成和调用
在使用 PySide6 进行开发时,管理应用程序的资源(如图标、图片、字体、样式表、音视频等)是一个常见的任务。PySide6 提供了一个工具 pyside6-rcc,它能够将资源文件(.qrc)编译成 Python 模块,然后…...

软件工程:如何做好软件产品
1、什么是产品 从项目到产品 产品:满足行业共性需求的标准产品。即要能够做到配置化的开发,用同一款产品最大限度地满足不同客户的需求,同时让产品具有可以快速响应客户需求变化的能力。 好的产品一定吸收了多个项目的共性,一定是…...
AI 模型分类全解:特性与选择指南
人工智能(AI)技术正以前所未有的速度改变着我们的生活和工作方式。AI 模型作为实现人工智能的核心组件,种类繁多,功能各异。从简单的线性回归模型到复杂的深度学习网络,从文本生成到图像识别,AI 模型的应用…...

【OpenGL学习】(五)自定义着色器类
文章目录 【OpenGL学习】(五)自定义着色器类着色器类插值着色统一着色 【OpenGL学习】(五)自定义着色器类 项目结构: 着色器类 // shader_s.h #ifndef SHADER_H #define SHADER_H#include <glad/glad.h>#inc…...
软珊瑚成分 CI-A:靶向口腔癌细胞的 “氧化利剑” 与 ERK 密码
在生命科学探索的浩瀚星海中,癌症研究始终是最为耀眼却又充满挑战的领域之一。口腔癌,作为全球范围内日益严峻的公共健康问题,尤其在中南亚、美拉尼西亚以及我国台湾地区,其发病率和死亡率持续攀升,如同隐藏在黑暗中的…...

静态相机中的 CCD和CMOS的区别
文章目录 CCD处理方式CMOS处理方式两者区别 首先根据 成像原理,CCD和CMOS的作用是一致的,都是为了将光子转化为数字图像,只是 转换的方式出现差异。 CCD处理方式 获取光子: 在电荷耦合器件(CCD)传感器中…...

LLM Agent 如何颠覆股价预测的传统范式
写在前面 股价预测,金融领域的“圣杯”之一,吸引了无数研究者和投资者。传统方法从技术指标到复杂的计量经济模型,再到机器学习,不断演进,但市场的高度复杂性、非线性和充斥噪声的特性,使得精准预测依然是巨大的挑战。大型语言模型(LLM)的崛起,特别是LLM Agent这一新…...

XXTEA,XTEA与TEA
TEA、XTEA和XXTEA都是分组加密算法,它们在设计、安全性、性能等方面存在显著区别。以下是它们的主要区别: 密钥长度 TEA:使用128位密钥。 XTEA:通常使用128位或256位密钥。 XXTEA:密钥长度更灵活,可以使用任…...
#开发环境篇:postMan可以正常调通,但是浏览器里面一直报403
本地header代理下面内容即可 headers: { // 添加必要的请求头 ‘Host’: ‘服务端域名’, ‘Origin’: https://服务端域名, ‘Referer’: https://服务端域名 }, devServer: {// 本地开发代理API地址proxy: {^/file: {target: https://服务端域名,changeOrigin: true, // 是否…...

我用Amazon Q写了一个Docker客户端,并上架了懒猫微服商店
自从接触了Amazon Q,我陆陆续续写了不少小软件,其中这个项目是一个典型的例子,自己平时来使用,也分享给一些 NAS 爱好者来用。 故事还要用上次折腾黑群晖说起,本意想把 NAS 和打印机共享二合一的,所以把闲着…...

PyCharm项目和文件运行时使用conda环境的教程
打开【文件】—【新建项目】 按照下图配置环境 可以看到我这个项目里,报错“No module named modelscope” 点击终端,输入命令 #显示所有的conda环境 conda env list #选择需要激活的conda环境 conda activate XXX在终端中,执行pip install …...