C++重载决议

article/2025/9/21 8:51:03

目录

  • 前言
  • 一些与overload易混淆的概念
    • override
    • 运算符重载
  • 为什么需要重载决议?
    • 声明函数重载
  • 什么是重载决议?
    • 什么不是函数重载?
  • 重载决议之过程
    • 函数重载 && 函数模板
    • before 重载决议
    • more details
    • type conversion(类型转换)
    • rank(排名)
    • 其他两个排名
    • 解决ambiguous function call
    • 当最佳匹配不是你想要的

前言

这篇博客用来总结overload resolution,这是一篇关于重载决议的演讲,very nice。

一些与overload易混淆的概念

override

  • 子类继承父类,有可能你需要让父类某些函数为虚函数,让子类重写虚函数,形成多态。
  • 但是你的子类,可能虚函数写错了,就无法形成多态。你可以在子类虚函数上加上override,编译器会帮助你检查该函数是否重写了。
  • overload 是编译期多态,根据date type来区分调用哪个重载版本。
  • override是运行期多态,根据object的动态类型区分调用哪种method。

运算符重载

  • 运算符重载实际上不算是通常意义上的重载,它总是有一个operator加上一个要重载的运算符,这是它的名字。
  • 运算符重载实现的机制也是重载决议。

为什么需要重载决议?

  • 当多个函数对于某个调用都是可见的(visible),且它们具有相同的名字,不同的parameter-list(形参列表)时,重载就会发生。
  • 函数重载可以避免名字过长。比如你有你个函数想用作int,char:
doThingsToInt(); // 不使用函数重载,名字就可能很长
doThingToChar();doThing(); //函数重载

声明函数重载

  • 函数重载适用范围很广,构造函数,成员函数,普通函数等等
  • 当两个函数:
  1. 对于当前函数调用都是可见的,
  2. 拥有相同的名字,不同的参数列表,
  3. 这两个函数互为重载。
  • 声明重载函数的顺序并不会改变调用的重载函数。你先声明的函数不一定优先级更高。
/* example 1*/
int dothing(int);         // one
int dothing(int, double); // twoint main(){dothing(1);			  //调用onedothing(1, 3.14);	  //调用two
}
  • 上面就是最普通的函数重载,它的调用也符合我们的预期。

什么是重载决议?

  • 重载决议就是选择最匹配的重载函数的过程。
  • 重载决议是面对函数重载时编译器必须做出的决定。
  • 重载决议完全发生在编译期。
  • 重载决议只会考虑实参的data type,和形参的type。该过程只关系类型的匹配程度,它不关心实际传过去的值!!只关心类型,只关心类型,只关心类型。
  • 如果两个函数拥有同样的rank(等级),编译器就无法挑选出更匹配的那个,这就ambiguous。

模板函数也参与重载决议。。。因为模板函数和非模板函数也能形参函数重载,
这让重载决议有点复杂,

  • 如果模板和非模板函数拥有相同的rank,那么非模板函数会被选择。(这也正常)

什么不是函数重载?

  • 我觉得这点也很简单,只要知道上面的几点就行。
  1. 当两个函数只有返回值不同时,无法形成重载。返回值不是必须的,也就是说你可以不使用返回值,这样无法看出你想选择哪一个。(我们可以通过SFINAE实现一些“返回值版的函数重载”,interesting)。
  2. 当两个函数签名式(signature,函数名字+参数列表)相同,但是有不同的default value时,也不会形成重载。因为函数重载只看类型,不会看实际值。
  3. 当两个函数签名式相同,但是一个为static时,也不会形成函数重载。。。。

重载决议之过程

  • 你可能认为重载决议very easy,不就是选择更匹配的吗?
  • 也没那么容易。当你考虑到各种转换,像什么指针,引用的转换,左值引用,右值引用转换,模板实参推导,等等一系列的时候,情况变的糟糕了。。。(尤其是考虑转换)
  • 而且重载决议可能会选择错误的匹配,这时候需要你去调试。弄清楚编译器是怎样执行的

函数重载 && 函数模板

  • 确实,这两个东西很相似。
  • 什么时候使用函数重载,什么时候使用函数模板呢?

当实现需要根据参数类型而变化时,使用函数重载。
当实现完全类似而与参数类型无关时,使用函数模板。
实际上,当我们谈论函数模板时,往往谈论的是函数重载而非函数模板。因为函数模板不支持偏特化。

before 重载决议

  • 在重载决议发生之前,编译器会进行一种procedure叫做name lookup(所谓名称查找,这是固定术语)。
  • name lookup会去找到关于你的调用可见的所有函数声明。
  1. name lookup 也许需要argumemt dependent lookup(所谓ADL,依据实参,编译器会自动去查看实参所在的命名空间,即使你没有显示说明。)
  2. 模板函数 也许需要 template argument deduction (模板实参推导,推导出函数模板的类型)。当编译器找到模板函数,它会去尝试推导出参数类型,然后放入overload set。
  • 所有可见的函数声明的列表形成一个集合,叫做overload set。注意,这一步形成的overload set可能是非常大的,因为它只是简单的查找名称,查找全局,然后是各种命名空间,实参命名空间等等。

more details

  • 第一步,编译器构造一个overload set,然后将其中所有的函数声明放入一个叫做candidates(候选人)的列表中。
  • 第二步,编译器会将那些非法的函数声明从candidates列表中去掉,所谓“非法”在C++标准中被叫做“not viable”。
  • 编译器一般会在两个方面判断一个函数是否为“not viable”:
  1. 根据你传入的参数个数。如果函数声明的参数个数比你调用的参数个数少,那么该函数声明not viable,将该函数声明从candidates列表移除。如果函数声明的参数个数多,且多余的参数没有default value,那么也是not viable。
  2. 判断参数类型。如果函数声明中的参数类型与实参的类型不匹配,即使考虑到隐式类型转换,那么就是not viable的。
void dothing(std::string);
void dothing(int);
void dothing(int, double);int main(){dothing(3);      //调用第二个,因为第三个参数个数太多,第一个无法将int转换成std::string。dothing(3, 3.14);//调用第三个return 0;
}
  • 此时,在candidates列表中,所有的候选者都是可行的,但是我们要去寻找一个best match的。
  • 第三步,找到最匹配的候选者。我们要通过一系列C++标准对candidates列表进行rank(排名)。
  • 如果只有一个候选者获得最高的rank,或者candidates列表只剩下了一个候选者,那么排名结束,这就是我们想要的。如果有两个或者多个候选者排名相同,那么我们就要进入所谓的tie breaker(决胜局)。

type conversion(类型转换)

  • 实际上,重载决议的最终阶段还是回到了类型转换上来。
  • 类型转换,即将一种类型转换成另外一种类型。
  • 我们有显示转换,static_cast,const_cast等等,我们还有隐式转换,如float到int。

rank(排名)

  • 让我们通过标准来进行排序。

标准1,2,3
标准4,5

  • 上面就是5个转换的排名。
  • 第三个就是对非const到const 和非volatile到volatile的转换。
  • 第四个是所谓的整形提升。

栗子2:

void dothing2(char){};  // one
void dothing2(long){};   // twoint main(){dothing2(42);  //选择哪一个重载版本? one or two ?return 0;
}
  • 编译器认为这是ambiguous!!
  • but,why?标准说,整形提升的排名高于普通的转换,为什么不是选择int到long的“整形提升”?
  • 我们需要更仔细的查看标准,标准是说,整形提升是某一种长度比int小的intergral提升为int,你会发现整形提升往往是提升到int,而标准认为int到long不是整形提升,而是转换!!
  • 所有的整形提升必须在标准中,如果标准没有提到,那么就是转换!!

整形提升

在这里插入图片描述

其他两个排名

  • C++标准中还说了其他两个转换的排名。

其他的转换

  • user defined conversion是转换成class类型or从class类型转换出去,

  • 这意味着,即使你使用std里面的各种class,编译器也会认为它们是用户自定义类型。

  • 如果两个函数声明拥有同样的rank,那么它们需要进入tie breaker。而我们的tie breaker也有一些规则:

  1. 如果一个模板和一个非模板拥有同样的rank,那么选择非模板。
  2. 如果一个隐式转换需要的“steps”更少,那么我们选择这个。
  3. 对于C++20的concept新增一个tie breaker的规则:如果一个candidate的concept更严格,那么选择这一个。
  4. 注意,该3个规则不会影响rank,只有引入tie breaker的时候才会启用该三点规则。
  • 如果tie breaker同样不能分出胜负,那么就会报错。

栗子4:

void dothing4(char val){};  // onetemplate <class T>
void dothing4(T val){};    // twoint main(){dothing4(42);  //选择哪一个重载版本? one or two ?return 0;
}
  • 选择第二个版本。因为T会被实参推导规则推导成int,这是一个完美匹配。
  • 所以我们应该尽量避免将函数模板和普通函数放在一个overload set中,因为模板往往形成最佳匹配。
  • 注意,模板大于非模板只有在tie breaker中才会被使用。

解决ambiguous function call

  • 增加或者删除一个重载函数
  • 让构造函数成为implicit
  • 为模板函数的参数添加约束(C++20有concept),这样通过SFINAE就会帮助我们排除某些函数模板。
  • 将argument显示转换,而非使用隐式转换。

例如,static_cast<>,explicitly构造一个对象,使用string(“hello”)作为参数,而非传递一个string literal(string字面量)。

当最佳匹配不是你想要的

  • 尝试利用规则制造出ambiguous,然后就能推导出最佳匹配位于哪一个rank,然后就可以进行更细致的推导。
  • 尝试去理解编译器如何看待candidates
  • 更改某些arguments的类型,

栗子6:

void dothing6_A(double, int, int){}      // one
void dothing6_A(int, double, double){}   // twodothing6_A(4, 5, 6);  //选择哪一个? one or two ?void dothing6_B(int, int, double){}     // three
void dothing6_B(int, double, double){}  // fourdothing6_B(4, 5, 6);   //选择哪一个? three or four ?
  • dothing6_A的调用是ambiguous。你可能会选择one,因为第一个有两个int,可惜这不是编译器认为的。
  • 编译器每次都会为每个参数进行rank。第一回合,为第一个参数排序,int是更好的排序,所以two不能是最好匹配。第二回合,one取胜。所以two不可能是最好匹配。到此为止,编译器结束重载决议,没有最好匹配。(这类似与某些游戏,没有平局,只有双输。)
  • 而在dothing6_B中,第一回合,three和four的第一个参数都是int,它们是同样的rank。第二回合,three取胜,因为它的int更匹配。而第三回合,three和four又是同样的rank。综合以上,three在第二回合取胜,且其他回合没有更差,所以选择重载版本three。
  • amazing。

栗子7:

void dothing7_A(int&){}      // one
void dothing7_A(int){}       // twoint x = 42;
dothing7_A(x);  //选择哪一个? one or two ?void dothing7_B(int&){}     // three
void dothing7_B(int){}      // fourdothing7_B(42);   //选择哪一个? three or four ?
  • 首先我们来看第一个调用,将int&绑定到int,是一个最佳匹配,不是一个转换,而按值传递同样是最佳匹配,所以该调用时ambiguous。
  • 第二个调用,选择four。因为42是一个常量,不能将非const左值引用绑定到常量上。因此four的调用不合法,只能选择three。

栗子8:

void dothing8_A(int&){}      // one
void dothing8_A(int&&){}       // twoint x = 42;
dothing8_A(x);  //选择哪一个? one or two ?void dothing8_B(int&){}     // three
void dothing8_B(int&&){}      // fourdothing8_B(42);   //选择哪一个? three or four ?
  • 在第一个调用中,选择one。因为将右值引用绑定到左值上。所以two是非法的调用,所以one胜出。
  • 在第二个调用中,同样的,我们无法将非const左值引用绑定到常量上,所以four胜出。

栗子9:

void dothing9(int&){cout << "int&" << endl;   //one}void dothing9(...){cout << "..." << endl;    //two}struct MyStruct {int data_ : 5;};MyStruct obj;dothing9(obj.data_);  //调用哪一个?
  • 编译器会调用one。因为位域(bit field)在C++中不认为是一种type,位域不在type system中,所以当你传递一个位数为5的位域时,编译器会认为你传递了一个int类型,所以one是完美匹配。
  • However,当你真正编译的时候,编译器会报错,因为你无法将int&绑定到位域上。
  • 即使你加入一个const int&,依然会报错,因为const int&版本会在重载决议时被非const干掉。

http://chatgpt.dhexx.cn/article/p2qp2QCN.shtml

相关文章

重载

实质&#xff1a;重载的实质就是写一个重载函数语法&#xff1a;函数类型 operator 运算符名称 {对运算符的重载处理}说明&#xff1a;如果要对加号进行重载&#xff0c;则 “operator ” 就是函数名。方法&#xff1a; &#xff08;1&#xff09;成员函数法 定义&#xff1a…

重载运算符

输入带有空格的字符串 cin.getline(a[i]) 运算符重载的语法 定义一个重载的运算符与定义一个函数类似&#xff0c;只是这个函数的名称必须以operator开头。 运算符重载函数的一般形式为&#xff1a;类型 类名::operator 重载的运算符(参数表) {函数体 }参数的个数由以下两个…

Python——运算符重载

Python——运算符重载 1.加法运算符重载和减法运算符重载2.__str__()方法重载和__ge__()方法重载3.索引的切片重载 运算符重载指的是将运算符与类方法关联起来&#xff0c;每个运算符对应一个指定的内置方法。 Python通过重写一些内置方法&#xff0c;实现了运算符的重载功能。…

C++ 运算符重载

前言 本文引用于“C语言中文网”&#xff0c;我整理出来放在博客&#xff0c;方便大家共同学习。所有知识点和代码均已亲测可用&#xff0c;如有疑问&#xff0c;可提出&#xff0c;一起讨论学习。 本章内容&#xff1a; 1. C运算符重载的概念和原理 2. C重载&#xff08;C重…

配置使用Navicat或PLSQL可视化工具远程连接Oracle

写在前面 本文讲解的是在没有 Oracle 环境&#xff0c;如何使用可视化工具配置远程连接服务器 Oracle 数据库。 目录 写在前面一、概述二、下载 Oracle Instant Client三、配置环境变量四、配置监听五、可视化工具环境配置5.1、Navicat5.2、PLSQL 一、概述 很多时候&#xff0c…

Windows系统远程连接Linux系统操作

远程连接服务器管理时&#xff0c;系统不同可分为两种&#xff1a;一是Linux系统和Mac系统或者Linux系统之间连接&#xff1b;二是Windows系统连接到Linux系统 第一种情况下&#xff1a;在Linux系统和Mac系统下可以相互用命令"ssh [-p] 22 连接主机名主机IP地址"连…

怎么删除远程桌面连接计算机,win10系统强制删除远程桌面连接软件的方法

大家都知道win10系统中&#xff0c;自带有远程桌面连接软件&#xff0c;能够帮助我们实现远程控制其他计算机进行一系列操作&#xff0c;但是有些用户可能用不到这个软件&#xff0c;就想要强制删除远程桌面连接软件&#xff0c;那该怎么操作呢&#xff0c;小编这就给大家带来w…

被远程连接的windows一定要设置账号的密码吗

背景 我们有个windows系统&#xff08;方便表述叫P)&#xff0c;比如win10的&#xff0c;平时使用的用户名是a&#xff0c;没有登录密码&#xff0c;平时使用P的过程是允许没有密码登录操作系统的。 但是&#xff0c;如果使用另外一台windows远程连接它&#xff0c;就得为a设…

SecureCRT--远程连接工具

SecureCRT简介 Linux一般作为服务器使用&#xff0c;而服务器一般放在机房&#xff0c;你不可能在机房操作你的Linux服务器。这时我们就需要远程登录到Linux服务器来管理维护系统。 Linux系统中是通过SSH服务实现的远程登录功能&#xff0c;默认ssh服务端口号为 22。Window系…

【rdesktop】超实用的ubuntu远程连接windows软件

个人定制命令行 rdesktop 192.168.50.118 -u zhankun -p LPjj1234 -z -a 16 -x lan -r clipboard -g 1920x105000 -r sound:local -r disk:share/home/lyp/zk_share1、ubuntu端 sudo apt install rdesktop2、Windows端 1.计算机—属性—远程设置—远程&#xff0c;勾选&…

Mac远程控制软件有哪些?Macos好用的远程桌面连接软件推荐

Mac功能强大&#xff0c;但是远程协助有点麻烦&#xff0c;此时需要专业的 Mac远程控制软件。远程桌面自带系统连接不太好用&#xff0c;一般不懂计算机的人很难成功。还推荐使用专用的远程控制软件实现&#xff0c;操作简单&#xff0c;不需要很多计算机知识。就下载量和好评率…

远程连接——Win连接远程Linux的软件

SSH客户端主要用来远程访问服务器。 因为像Linux之类的系统要想在Windows上面存在&#xff0c;基本都是通过虚拟机&#xff0c;就好比搭建了一个服务器&#xff0c;你要连接这个系统&#xff0c;自然需要用到一些工具&#xff0c;而SSH客户端就属于这样的工具&#xff0c;或者…

有服务器可以远程连接电脑软件,支持远程控制电脑的工具有哪些?这几款软件值得一试!...

对于普通人来说,如果自己身处于一线工作城市,与异地的家人进行聊天时,他们遇到了电脑上的某种问题时,而我们直接靠说话或文字,并不能直接来解决他们所遇到的问题,他们自己又根本不会操作,此时直接控制他们的电脑就是一个非常棒的方法,既可以解决了问题,不需要多费口舌…

centos 7.6上面安装向日葵远程连接软件

原文&#xff1a;https://www.cnblogs.com/yunjisuan1024/p/11731369.html 下载&#xff1a;https://sunlogin.oray.com/download/ 安装教程&#xff1a;https://service.oray.com/question/11017.html 依赖问题&#xff1a;https://service.oray.com/question/8286.html Cen…

Navicat远程连接MySQL服务器

文章目录 一、准备二、配置Navicat允许远程连接MySQL数据库1、使用Navicat直接连接MySQL2、使用 Navicat 通过 SSH 远程登录后再本地方式连接 MySQL3、查看连接 为什么使用ssh登录1.便捷性 Navicat:数据库可视化工具 一、准备 一台开启 SSH 登录的 Linux 服务器 或 已开启远程…

Linux远程连接和软件安装

Linux远程连接和软件安装 文章目录 Linux远程连接和软件安装1.Linux远程连接简介2.远程连接准备3.Linux进程和程序4.moba实现远程连接步骤:5.软件安装1)yum命令:在线安装2)rpm命令:直接安装3)tar:解压安装4)unzip:解压安装 1.Linux远程连接简介 SSH协议(Secure Shell):专为远程…

推荐|6 款免费又好用的远程管理工具

「 读者福利&#xff01;2 TB 各类技术资源免费赠送 」 大家好&#xff0c;我是你们的民哥。 2020年开年&#xff0c;让大家意想不到的是&#xff0c;本以为很短暂的假期&#xff0c;却因为这个疫情一推再推&#xff0c;很多人做梦也没有想到&#xff1a;能在家呆这么长时间&am…

推荐几款主流好用的远程终端连接管理软件

一、介绍 远程终端连接管理软件是管理服务器、虚拟机等远程计算机系统不可或缺的工具之一&#xff0c;它可以通过网络连接到另一台计算机&#xff0c;以执行命令、编辑文件或进行其他管理任务&#xff0c;下面我将为大家介绍几款主流好用的远程终端连接管理软件&#xff0c;并…

SSO单点登录原理及实现

一、SSO简介 单点登录&#xff08; Single Sign-On , 简称 SSO &#xff09;是多个相关但独立的软件系统访问控制的一个属性。通过使用该属性&#xff0c;用户登录与单个ID和密码来访问所连接的一个或多个系统&#xff0c;而不使用不同的用户名或密码&#xff0c;或在某些配置…

单点登录三个方法及原理:共享Session、基于OpenId的单点登录、基于Cookie的OpenId存储方案

单点登录三个方法及原理&#xff1a;共享Session、基于OpenId的单点登录、基于Cookie的OpenId存储方案 单点登录在现在的系统架构中广泛存在&#xff0c;他将多个子系统的认证体系打通&#xff0c;实现了一个入口多处使用&#xff0c;而在架构单点登录时&#xff0c;也会遇到一…