参考《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 步:确定最佳匹配函数。通常,从最佳到最差的顺序如下所述:

  1. 特征标完全匹配
  2. 类型需经隐式提升转换,例如 charshort 自动转换为 intfloat 自动转换为 double
  3. 类型需经隐式标准转换,例如 int 转换为 charlong 转换为 double
  4. 类型需经隐式自定义转换,例如类中用户定义的类型转换。

依此规则,函数 #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;第二次调用时根据重载解析策略选择了模板函数 #1double 版本,属于模板函数的隐式实例化;第三次调用的 <> 指出,编译器应该选择模板函数,此时编译器会查看调用函数时的实参类型来进行实例化,也属于模板函数的隐式实例化;第四次调用的 <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++ 函数重载解析策略的更多相关文章

  1. C++解析(16):友元与类中的函数重载

    0.目录 1.友元的尴尬能力 2.类中的函数重载 3.小结 1.友元的尴尬能力 什么是友元? 友元是C++中的一种关系 友元关系发生在函数与类之间或者类与类之间 友元关系是单项的,不能传递 友元的用法 ...

  2. C++ primer(八)--内联函数 引用变量 引用传递函数参数 函数重载/模板/模板具体化

    一.内联函数     常规函数和内联函数的区别在于C++编译器如何将他们组合到程序中.编译过程的最终产品是可执行程序--由一组机器语言指令组成.运行程序时,操作系统将这些指令载入到计算机内存中,因此每 ...

  3. C++ 函数重载,函数模板和函数模板重载,选择哪一个?

    重载解析 在C++中,对于函数重载.函数模板和函数模板重载,C++需要有一个良好的策略,去选择调用哪一个函数定义(尤其是多个参数时),这个过程称为重载解析. (这个过程将会非常复杂,但愿不要遇到一定要 ...

  4. c++函数重载---2

    原创博客:转载请标明出处:http://www.cnblogs.com/zxouxuewei/ 写在前面: 函数重载的重要性不言而明,但是你知道C++中函数重载是如何实现的呢(虽然本文谈的是C++中函 ...

  5. C++的函数重载 转

    ——每个现象后面都隐藏一个本质,关键在于我们是否去挖掘 写在前面: 函数重载的重要性不言而明,但是你知道C++中函数重载是如何实现的呢(虽然本文谈的是C++中函数重载的实现,但我想其它语言也是类似的) ...

  6. C++重载解析

    重载解析(overloading resolution)的规则决定了编译器为一个函数调用选用哪个函数定义.一般过程如下: 将名称相同的函数/模板函数找到并创建候选列表 从中挑选参数数目正确,符合完全匹 ...

  7. C++普通函数与模板函数以及特化函数重载的优先级问题

    在面对C++模板的时候,需要十分注意,因为模板的复杂性有很多情况,所以最好学习模板的方法我个人认为就是用到就去学,用不到就尽量别去看各种奇门怪技,因为你就算看了,好不容易搞懂模板的实现内部了,包括元编 ...

  8. 小猪猪C++笔记基础篇(六)参数传递、函数重载、函数指针、调试帮助

    小猪猪C++笔记基础篇(六) ————参数传递.函数重载.函数指针.调试帮助 关键词:参数传递.函数重载.函数指针.调试帮助 因为一些事情以及自己的懒惰,大概有一个星期没有继续读书了,已经不行了,赶紧 ...

  9. C++函数重载实现的原理以及为什么在C++中使用用C语言编译的函数时,要在函数名称前面加上extern "C"声明

    C++相对于C语言而言支持函数重载是其极大的一个特点,相信在使用C语言的时候大家如果要写一个实现两个整型数据相加的函数还要写一个浮点型数据相加的函数,那么这两个函数的名字绝对不可以一样,这样无疑在我们 ...

随机推荐

  1. Java面试题(四)--RabbitMQ

    1.MQ有哪些使用场景?(高频) 异步处理:用户注册后,发送注册邮件和注册短信.用户注册完成后,提交任务到 MQ,发送模块并行获取 MQ 中的任务. 系统解耦:比如用注册完成,再加一个发送微信通知.只 ...

  2. 故障案例 | lsof是怎么"影响"MySQL计算打开文件句柄数的

    欢迎来到 GreatSQL社区分享的MySQL技术文章,如有疑问或想学习的内容,可以在下方评论区留言,看到后会进行解答 lsof中附加不同参数产生的结果也不同,小心"踩坑". 1. ...

  3. Spherical类定义和实现

    此类是一个全景摄像机视角,书上介绍了详细原理.直接给实现代码. 类声明: #pragma once #ifndef __SPHERICAL_HEADER__ #define __SPHERICAL_H ...

  4. Apache DolphinScheduler 1.2.0 使用文档(1/8):架构及名词解释

    本文章经授权转载,原文链接: https://blog.csdn.net/MiaoSO/article/details/104770720 目录 1. 架构及名词解释 1.1 DolphinSched ...

  5. Dart 异步编程(三):详细认识

    基本概念 普通任务按照顺序执行:异步任务将在未来的某个时间执行. 实际演示 void main() { // waitFuture 函数是一个异步函数,阻塞会发生在函数内部 waitFuture(); ...

  6. virtio_net设备的校验和问题

    我们来看一个virtio_net设备的校验和配置: [root@10 ~]# ethtool -K eth0 tx-checksumming on //caq:大写的K用来调整feature [roo ...

  7. java基础———打印三角形

    代码 public static void main(String[] args) { for (int i = 1; i <= 5; i++) { for (int j = 5; j > ...

  8. 如何实现Windows平台RTMP播放器/RTSP播放器播放窗口添加OSD文字叠加

    好多开发者在做Windows平台特别是单屏多画面显示时,希望像监控摄像机一样,可以在播放画面添加OSD台标,以实现字符叠加效果,大多开发者可很轻松的实现以上效果,针对此,本文以大牛直播SDK (Git ...

  9. 使用『jQuery』『原生js』制作一个导航栏动效 —— { }

    效果 HTML部分 <body> <nav> <div id="nav1">导航1</div> <div id="n ...

  10. yum install lrzsz

    yum install lrzsz rz:从本地上传文件至服务器 sz filename:从服务器下载文件至本地