参考《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
将被解释