C++重载解析

来源:互联网 发布:吉祥抽奖软件 编辑:程序博客网 时间:2024/05/29 18:02

B.1何时应用重载解析

重载解析可以看作是函数调用整个完整处理过程的一部分。首先,如果是通过指针或者成员函数指针来进行调用,就不会进行重载解析:因为究竟调用哪一个是在运行期由指针(实际所指的对象)来决定的。另外,类似函数的宏不能被重载,因此就不会进行重载解析。

B.2简化过的重载解析

重载解析通过比较调用实参和候选参数的匹配程度,来对所有的可行候选函数进行分级,对于匹配级别高的候选函数,它每个参数的匹配程度都不能低于匹配级别低的候选函数的相应参数的匹配程度。下面的例子说明了这一点:

void combine(int,double);void combine(long,int);int main(){combine(1,2);//二义性}

这个例子中,因为一个候选函数可以最佳匹配第一个参数,而第二个候选函数可以最佳匹配第二个参数,。我们可能觉得:从某种意义上而言,int与long的相似度要比int与double相似度高(因此选择第二个),但是C++并不会试图度量这种涉及到多个调用实参的相似度,从而引发二义性。

根据分级原则,我们可以对下面的匹配精心分级(从最佳到最差):

1) 完美匹配。(参数的类型和实参(表达式)的类型相同,或者参数的类型是指向实参类型的引用(也可以增加const或者volatile限定符))

2) 有细微调整的匹配。

3) 发生提升的匹配。(提升是一种隐式类型转换,它包含包占位少的整数类型(bool,char,short或者枚举)转换为占位多的类型(int,unsigned int,long或者unsigned long),还包括从float到double的转换。

4) 发生标准转换的匹配。(这包含任何种类的标准转换,但并不包含隐式调用的类型转换运算符和单参数构造函数)

5) 发生用户自定义转型的匹配。(允许任何种类的隐式转换)

6) 和省略号的匹配。(省略号可以匹配任何类型(但匹配非POD(plain old data)类型会导致未定义行为))

下面的例子很好的说明上面的匹配:

int f1(int)://(1)int f1(double);//(2)f1(4)//调用(1):精确匹配,而(2)需要一个标准转型int f2(int);//(3)int f2(char);//(4)f2(true);//调用(3):发生提升的匹配,true是bool类型,而(4)要求强行的标准转型class X{public:X(int);};int f3(X);//(5)int f3(...);//(6)f3(7);//调用(5):发生用户自定义转型的匹配,而(6)要求和省略号进行匹配

我们知道,重载解析是在模板实参演绎之后才进行的;因此,演绎并会考虑上面的这些类型转换。下面的例子说明了这一点:

template <typename T>class MyString{public:MyString(T const*);//能够进行类型转换的构造函数...};template <typename T>MyString<T> truncate(MyString<T> const&,int);int main(){MyStirng<char> str1,str2;str1 = truncate<char>("Hello World",5);//OKstr2 = truncate("Hello World",5);//error}

在模板实参的演绎过程中,并不会考虑由但参数构造函数所提供的隐式转换。在各str2赋值的过程中,并不能找到任何可行的truncate()函数;因此根本就不会执行重载解析。

B.2.1成员函数的隐含实参

#include<stddef.h>class BadString{BadString(char const*);...//通过下标运算符来访问字符char& operate[]{size_t);//(1)char const& operate[]{size_t) const;//隐式转换为以null结束的字符串operate char*();//(2)operate char const*();...};int main(){ BadString str("correct");str[5] = 'c';//可能会产生重载解析二义性}

第一眼看来,关于表达式时str[5]的一切都是确定的。(1)处的下标运算符看起来也像是完美匹配。然而,如果我们仔细观察会发现,实参5的类型是int,而运算符所期望的是size_t(通常是无符号整数unsigned int 或者unsiged long),于是如果要匹配(1)的话,就需要进行一次标准转换,然而,还存在另一个可行的候选函数:内建(即相对于char×)的下标运算符。而且内建的下标运算符接受一个ptrdiff_t类型的实参,在某些平台下ptrdiff_t等于int,所以该类型是实参5的完美匹配。因此,就隐式实参(指str,就是隐含的×this)而言,尽管内建的下标运算符可能是一个不太好的匹配(会先进性用户自定义的转换),但它应该比在(1)处的匹配更好。从而就会出现二义性。

在重载解析中,隐含的this参数的地位和显示参数的地位一样。大多数情况下并不会产生影响,但是仅偶尔会导致出任意料的结果,如同上面的例子。

B.2.2细化完美匹配

如果你针对引用类型和没有引用的类型进行重载,一样完美的两个匹配也可以导致二义性:

对于T类型的右值,T和T  const &的匹配程度是一样的。

对于T类型的左值,T和T&的匹配程度一样。

void repotr(int);  //(1)void report(int&);  //(2)void report(int const&);  //(3) int main(){for(int k = 0; k<10; ++k){report(k);                //二义性:(1)和(2)的匹配度一样}report(42);          //二义性:(1)和(3)的匹配度一样}

B.3重载的细节

B3.1非模板优先

当重载解析的其他各个方面都是等同的时候,非模板优先于由模板产生的实例(无论产生自泛型模范的实例,还是显示特化所提供的实例)。

如果这种选择是在两个模板之间进行,那么将会选择特化程度更高的模板。

B.3.2转型序列

通常而言,一个隐式转型可以有一系列子转型构成。考虑下面的例子:

class Base{public:operate short()const;};class Derived:public Base{};void count(int);void process(Derived const& object){count(object);}
调用是正确的,因为object对象可以隐式的转型为int,然而,这个转型需要进行下面的几个子步骤:

·1.object对象从Derived const到Base const的转型。

2.从(由1获得的)Base const 到short的用户自定义转型。

3.从short到int的提升。

这也是使用的最广泛的转型序列:先进行一个标准转型,然后进行一个用户自定义转型,最后再进行一个标准转型。我们应该知道:尽管用户自定义转型只能有一个,但标准执行却可以是一个或多个。

重载解析的另一个重要原则是:如果转型序列A是的子序列,那么将会优先使用A所对应的转型。例如前面的例子有另一个候选函数:

void const(short);

那么调用count(object)将会优先使用上面的这个好像函数,因为它并不需要进行上述步骤3。

B.3.3指针的转型

·从指针到bool类型的转换

·从任意的指针类型到void×的转型。

`派生类指针到基类指针的转型。

·基类成员指针到派生类成员指针的转型。

首先,任何其他的标准转型都要优于bool类型的转型。

对于普通指针的转型,从派生类指针到基类指针的转型要优于到void×的转型。另外,如果可行函数的转型涉及到类继承体系中的多个类,那么将会优先选择派生路径最短的转型。这条规则(大体的)适用于成员指针。

B.3.4仿函数和代理函数

在查找玩函数名称、建立一个初始化重载集之后,还会发生一些变化。于是,当调用表达式引用的是一个类对象,而不是一个函数,就会出现比较有意思的情况。在这种情况下,可能会添加两种函数.

第一个添加是很直接易懂的:把任何operate()都添加到重载集中,具有这个运算符的对象通常称为仿函数。

第二个添加发生在:某个clas类型对象包含一个到函数类型指针的转型运算符。在这种情况下,就会把一个代理函数(也称为哑函数)添加到重载集中。值得注意的是:这个候选的代理函数除了具有显示声明的参数之外,还具有一个隐含参数,隐含参数的类型是转型函数所指派的类型。下面的例子说明这些概念:

typedef void FuncType(double,int);class IndirectFunctor{public:...operator()(double,double);operator FuncType*()const;};void active(IndirectFunctor const& funcObj){funcObj(3,5);//错误:二义性}

可行候选函数包含operator()成员和一个代理函数。就隐含参数而言,代理函数的匹配不如operator()(因为代理函数需要进行一次用户自定义的转型);但就最后一个参数而言,代理函数的匹配优于operator()。因此,我们不能对这两个进行排序,从而导致了二义性。

B.3.5其他的重载情况

第一种情况:需要函数地址的时候。

int n_elements(Matrix const&);//(1)int n_elements(Vector const&);//(2)void compute(){...int (*funcPtr)(Vector const&) = n_elements;//选择(2)...}

另一种发生在初始化的时候。//情况复杂,省略