C++ 函数重载解析策略
参考《C++ Primer Plus》(第6版)中文版,Stephen Prata 著,张海龙 袁国忠译,人民邮电出版社。C++ 使用重载解析策略来决定为函数调用使用哪一个函数定义。重载解析过程大致分为如下三步:
- 第 1 步:创建候选函数列表,只要求函数名一样即可,对函数特征标以及是否为模板函数无要求;
- 第 2 步:在上一步的基础上创建可行函数列表,包含特征标完全匹配的常规函数或模板函数、以及实参隐式转换后完全匹配的常规函数或模板函数,这些都是参数数目正确的函数;
- 第 3 步:在上一步的基础上确定最佳匹配函数,若有则使用它,若没有则该函数调用失败。
下面以一个例子来说明这个重载过程:
//全部函数原型
void may(int); //原型#1
float may(float, float = 3); //原型#2
void may(char); //原型#3
char * may(const char *); //原型#4
char may(const char &); //原型#5
template<class T> void may(const T &);//原型#6
template<class T> void may(T *); //原型#7
void may(char, double); //原型#8
void mbk(float); //原型#9
char mkk(int, char); //原型#10
int mck(char); //原型#11
double myk(float); //原型#12
void mpk(char); //原型#13
//函数调用
may('B');
//函数定义
...
重载第 1 步:创建候选函数列表。即函数名称为 may
的常规函数和模板函数,候选函数列表如下:
//重载第1步:创建候选函数列表
void may(int); //原型#1
float may(float, float = 3); //原型#2
void may(char); //原型#3
char * may(const char *); //原型#4
char may(const char &); //原型#5
template<class T> void may(const T &);//原型#6
template<class T> void may(T *); //原型#7
void may(char, double); //原型#8
重载第 2 步:创建可行函数列表。由于整数类型 char
不能被隐式地转换为指针类型 char *
,因此函数 #4
和函数 #7
都被排除,而函数 #8
因为参数数目不匹配也会被排除。进行完全匹配时,C++ 允许下表这些无关紧要的转换,表中 Type
表示任意类型,例如 char &
到 const char &
的转换也包含在内,表中 Type (argument-list)
意味着用作实参的函数名和用作形参的函数指针只要返回类型和参数列表相同,就是匹配的。
实参类型 | 形参类型 |
---|---|
Type |
Type & |
Type & |
Type |
Type [] |
Type * |
Type (argument-list) |
Type (*) (argument-list) |
Type |
const Type |
Type |
volatile Type |
Type * |
const Type * |
Type * |
volatile Type * |
根据此表可知,剩下的函数中包含特征标完全匹配的常规函数 #3
和 #5
、特征标完全匹配的模板函数 #6
(此时 T
可以被实例化为 char
)、实参隐式转换后完全匹配的常规函数 #1
和 #2
。可行函数列表如下:
//重载第2步:创建可行函数列表
void may(int); //原型#1
float may(float, float = 3); //原型#2
void may(char); //原型#3
char may(const char &); //原型#5
template<class T> void may(const T &);//原型#6
重载第 3 步:确定最佳匹配函数。通常,从最佳到最差的顺序如下所述:
- 特征标完全匹配;
- 类型需经隐式提升转换,例如
char
和short
自动转换为int
,float
自动转换为double
; - 类型需经隐式标准转换,例如
int
转换为char
,long
转换为double
; - 类型需经隐式自定义转换,例如类中用户定义的类型转换。
依此规则,函数 #3
和函数 #5
、函数 #6
都是特征标完全匹配的最佳匹配函数,函数 #1
需经隐式提升转换,函数 #2
需经隐式标准转换,由此各函数最佳匹配程度为:(#3, #5, #6) > #1 > #2
。当特征标完全匹配时,又有如下规则:
- 指向非
const
数据的指针和引用优先与形参为非const
指针和引用的函数匹配; - 优先与非模板函数匹配;
- 同为模板函数时,优先与较具体的模板函数匹配。
依此规则,非模板函数 #3
和 #5
最佳匹配程度要高于模板函数 #6
,即各函数最佳匹配程度为:(#3, #5) > #6 > #1 > #2
。最终出现了两个最佳匹配函数 #3
和 #5
,因此该函数调用失败,编译器将报错。
//重载第 3 步:确定最佳匹配函数
void may(char); //原型#3
char may(const char &); //原型#5
下面展开来说上述几条完全匹配时的规则。
第 1 条:指向非 const
数据的指针和引用优先与形参为非 const
指针和引用的函数匹配,这一点需明确,const
和非 const
之间的区别只适用于指针和引用。下面 4 个函数都与函数调用是完全匹配的:
//函数原型
void recycle(int); //原型#1
void recycle(const int); //原型#2
void recycle(int &); //原型#3
void recycle(const int &);//原型#4
//函数调用
int x = 5;
recycle(x);
//函数定义
...
- 如果这 4 个函数同时存在,则无法完成重载,编译器会报多义性匹配的错误;
- 如果只存在函数
#1
与#2
,则无法完成重载,编译器会报重复定义的错误; - 如果只存在函数
#1
与#3
,则无法完成重载,编译器会报多义性匹配的错误; - 如果只存在函数
#1
与#4
,则无法完成重载,编译器会报多义性匹配的错误; - 如果只存在函数
#2
与#3
,则无法完成重载,编译器会报多义性匹配的错误; - 如果只存在函数
#2
与#4
,则无法完成重载,编译器会报多义性匹配的错误; - 如果只存在函数
#3
与#4
,则函数调用时编译器将会选择#3
。
第 2 条:优先与非模板函数匹配,这一点比较简单,当完全匹配的函数中,一个是非模板函数,另一个是模板函数时,非模板函数将优于模板函数,显式具体化、显式实例化、隐式实例化都属于模板函数。
第 3 条:同为模板函数时,优先与较具体的模板函数匹配,找出最具体的模板的规则被称为函数模板的部分排序规则(partial ordering rules)。这意味着显式具体化优先于常规模板函数,都为常规模板函数时,编译器优先选择实例化时类型转换更少的那一个。以下面的程序为例,调用方式 recycle(&ink)
既与模板 #1
匹配,此时 Type
将被解释为 blot *
,也与模板 #2
匹配,此时 Type
将被解释为 blot
,因此将这两个隐式实例 recycle<blot *>(blot *)
和 recycle<blot>(blot *)
发送到可行函数池中。在选择最佳匹配函数时,#2
被认为是更具体的,因为它已经显式地指出,函数参数是指向 Type
的指针,相比于 #1
,它对类型的要求更加地具体,在生成过程中所需要的转换更少,因此调用方式 recycle(&ink)
实际会匹配版本 #2
。
//两个常规模板函数
template <class Type> void recycle(Type t); //原型#1
template <class Type> void recycle(Type * t); //原型#2
//调用程序包含如下代码
struct blot {int a; char b[10];};
blot ink = {25, "spots"};
...
recycle(&ink); //使用版本#2
//函数定义
...
部分排序规则的另一个示例程序如下,它与上一个例子有异曲同工之妙。由于模板 #2
做了特定的假设:数组内容是指针,对类型的要求更加地具体,因此在调用时第一个参数若传入指针数组 pt
,则将实际匹配函数 #2
。
//两个常规模板函数
template <typename T>
void ShowArray(T arr[], int n); //原型#1
template <typename T>
void ShowArray(T * arr[], int n); //原型#2
//调用程序包含如下代码
int things[6] = {13, 31, 103, 301, 310, 130};
int * pt[3] = {&things[0], &things[2], &things[4]};
ShowArray(things, 6); //使用版本#1
ShowArray(pt, 3); //使用版本#2
//函数定义
...
将有多个参数的函数调用与有多个参数的原型进行匹配时,编译器必须考虑所有参数的匹配情况。如果找到比其他可行函数都合适的函数,则选择该函数。一个函数要比其他函数都合适,其所有参数的匹配程度都必须不比其他函数差,同时至少有一个参数的匹配程度比其他函数都高。
在有些情况下,可通过编写合适的函数调用,来引导编译器做出程序员期望的选择。如下所示,其中模板函数返回两个值中较小的一个,非模板函数返回两个值中绝对值较小的那个。第一次调用时根据重载解析策略选择了非模板函数 #2
;第二次调用时根据重载解析策略选择了模板函数 #1
的 double
版本,属于模板函数的隐式实例化;第三次调用的 <>
指出,编译器应该选择模板函数,此时编译器会查看调用函数时的实参类型来进行实例化,也属于模板函数的隐式实例化;第四次调用的 <int>
显式指出,编译器应该使用模板函数的 int
实例化版本,此时属于模板函数的显式实例化。
#include <iostream>
//函数#1
template<class T>
T lesser(T a, T b)
{
return a < b ? a : b;
}
//函数#2
int lesser(int a, int b)
{
a = a < 0 ? -a : a;
b = b < 0 ? -b : b;
return a < b ? a : b;
}
//函数调用
int main()
{
using namespace std;
int m = 20;
int n = -30;
double x = 15.5;
double y = 25.9;
//使用#2,结果为20
cout << lesser(m, n) << endl;
//使用#1,double隐式实例化,结果为15.5
cout << lesser(x, y) << endl;
//使用#1,int隐式实例化,结果为-30
cout << lesser<>(m, n) << endl;
//使用#1,int显式实例化,结果为15
cout << lesser<int>(x, y) << endl;
return 0;
}
C++ 函数重载解析策略的更多相关文章
- C++解析(16):友元与类中的函数重载
0.目录 1.友元的尴尬能力 2.类中的函数重载 3.小结 1.友元的尴尬能力 什么是友元? 友元是C++中的一种关系 友元关系发生在函数与类之间或者类与类之间 友元关系是单项的,不能传递 友元的用法 ...
- C++ primer(八)--内联函数 引用变量 引用传递函数参数 函数重载/模板/模板具体化
一.内联函数 常规函数和内联函数的区别在于C++编译器如何将他们组合到程序中.编译过程的最终产品是可执行程序--由一组机器语言指令组成.运行程序时,操作系统将这些指令载入到计算机内存中,因此每 ...
- C++ 函数重载,函数模板和函数模板重载,选择哪一个?
重载解析 在C++中,对于函数重载.函数模板和函数模板重载,C++需要有一个良好的策略,去选择调用哪一个函数定义(尤其是多个参数时),这个过程称为重载解析. (这个过程将会非常复杂,但愿不要遇到一定要 ...
- c++函数重载---2
原创博客:转载请标明出处:http://www.cnblogs.com/zxouxuewei/ 写在前面: 函数重载的重要性不言而明,但是你知道C++中函数重载是如何实现的呢(虽然本文谈的是C++中函 ...
- C++的函数重载 转
——每个现象后面都隐藏一个本质,关键在于我们是否去挖掘 写在前面: 函数重载的重要性不言而明,但是你知道C++中函数重载是如何实现的呢(虽然本文谈的是C++中函数重载的实现,但我想其它语言也是类似的) ...
- C++重载解析
重载解析(overloading resolution)的规则决定了编译器为一个函数调用选用哪个函数定义.一般过程如下: 将名称相同的函数/模板函数找到并创建候选列表 从中挑选参数数目正确,符合完全匹 ...
- C++普通函数与模板函数以及特化函数重载的优先级问题
在面对C++模板的时候,需要十分注意,因为模板的复杂性有很多情况,所以最好学习模板的方法我个人认为就是用到就去学,用不到就尽量别去看各种奇门怪技,因为你就算看了,好不容易搞懂模板的实现内部了,包括元编 ...
- 小猪猪C++笔记基础篇(六)参数传递、函数重载、函数指针、调试帮助
小猪猪C++笔记基础篇(六) ————参数传递.函数重载.函数指针.调试帮助 关键词:参数传递.函数重载.函数指针.调试帮助 因为一些事情以及自己的懒惰,大概有一个星期没有继续读书了,已经不行了,赶紧 ...
- C++函数重载实现的原理以及为什么在C++中使用用C语言编译的函数时,要在函数名称前面加上extern "C"声明
C++相对于C语言而言支持函数重载是其极大的一个特点,相信在使用C语言的时候大家如果要写一个实现两个整型数据相加的函数还要写一个浮点型数据相加的函数,那么这两个函数的名字绝对不可以一样,这样无疑在我们 ...
随机推荐
- 跳转语句break、continue、return
1.break 语句 概念: break语句在循环中的作用是终止当前循环,在switch语句中的作用是终止switch. 示例: 输出结果: 2.continue 语句 概念: continue语句 ...
- 技术分享 | 在GreatDB分布式部署模式中使用Chaos Mesh做混沌测试
GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. 1. 需求背景与万里安全数据库软件GreatDB分布式部署模式介绍 1.1 需求背景 混沌测试是检测分布式系统不确定性.建 ...
- WTL_Freecell绿色版
WTL_Freecell绿色版-用户手册 1.程序特点和使用环境介绍 (1).版本信息 WTL_Freecell-Release05-v1.3-20190129 (WTL空当接龙绿色版v1.3),By ...
- 详解ConCurrentHashMap源码(jdk1.8)
ConCurrentHashMap是一个支持高并发集合,常用的集合之一,在jdk1.8中ConCurrentHashMap的结构和操作和HashMap都很类似: 数据结构基于数组+链表/红黑树. ge ...
- anltr 解析MYSQL
String sql = "select a as c,b as s,'zhange' F, 1/5 as e from t_order"; final MysqlQueryLex ...
- Python小游戏——外星人入侵(保姆级教程)第一章 07调整飞船速度 08限制飞船活动范围
系列文章目录 第一章:武装飞船 07调整飞船速度 08限制飞船活动范围 一.代码及演示 1.修改settings 修改文件:settings.py 点击查看代码 #渗透小红帽python的学习之路 # ...
- 免费内网穿透服务Localtunnel
Localtunnel 将为您分配一个唯一的可公开访问的 url,它将所有请求代理到您本地运行的网络服务器. 快速开始 全局安装 Localtunnel(需要 NodeJS)以使其在任何地方都可以访问 ...
- KingbaseES 数据库Windows环境下注册失败分析
关键字: KingbaseES.Java.Register.服务注册 一.安装前准备 1.1 软件环境要求 金仓数据库管理系统KingbaseES V8.0支持微软Windows 7.Windows ...
- 腾讯云实验室 Gitea 互动教程上线啦
如果你想学习.体验或是向他人演示开源的 Gitea 代码托管方案,那么接下来给你推荐一款神器. 使用腾讯云实验室免费获得 Gitea 实验环境,直接通过浏览器就可在 Ubuntu Server 20. ...
- 超详细的格式化输出(format的基本玩法)
一.format的基本玩法 一.什么是format format是字符串内嵌(字符串内嵌:字符串中再嵌套字符串,加入双引号或单引号)的一个方法,用于格式化字符串.以大括号{}来标明被替换的字符串 fo ...