天天看点

《C++代码设计与重用》——2.7 转型

本节书摘来自异步社区出版社《imperfect c++中文版》一书中的第2章,第2.7节,作者: 【美】martin d.carroll , margaret a.ellis,更多章节内容可以访问云栖社区“异步社区”公众号查看。

c++代码设计与重用

2.7 转型

程序库设计者必须充分重视隐式转型(implicit conversion)。在c++中,有两种方法可以用来定义从类型from到类型to的隐式转型。第一种,我们可以在类to中定义一个只含一个参数的构造函数(并且没有其他的缺省参数):

或者,我们可以在类from中定义一个转型操作:

假如上面这两个函数中的一个(也只能是一个)存在,那么当一个类型为from的参数传递给需要类型为to(或者const to&)的参数时,就会发生隐式转型:

2.7.1 多重所有权(multiple ownership)

如果from::operator to和to(const from&)两个操作都已经声明了,那么对f的调用将会是二义性的(ambiguous):

增加一个强制转型(指(to)from)并不能消除二义性;但多重所有权问题(如此命名是由于类from和类to都拥有转型操作)是可以很容易避免的,只要from和to的设计者更加小心谨慎,不要提供两个转型操作就可以了。

对称转型的存在—就是说,一个从from到to的隐式转型和一个从to到from的隐式转型都存在—并不会导致相同的二义性问题。

实际上,现实的类一般都提供对称转型。考虑一个用于表述正则表达式[sm77]的类regex,因为有必要用一个字符串来构造一个regex对象,并且有必要把一个正则表达式解释成一个字符串,所以一个真实的regex类提供了和string类的对称转型:

如果不能改变类string的定义,那么我们就只能都在类regex内定义这两个转型操作了。

2.7.2 敏感转型

一个从from到to的隐式转型,如果它表述的是一个从from到to的自然映射,或者是用户想要默许发生的转型,我们就说这个转型是敏感的(sensible)。实际上,大多数转型都应该是敏感的(我们会在下一节讨论特殊情况)。

下面让我们来考虑几个有关敏感性(sensibility)的例子。假设我们是一个数学程序库的设计者,在我们的程序库里,类rational和类complex分别描述有理数和复数。假如我们只考虑int、double、rational和complex 4种类型,就会有12种可能的隐式转型;那么,这些转型中哪些是敏感的呢?

complex到int的转型

很显然,这个转型不是敏感的(sensible)。尽管我们有可能定义任何从复数到整数的映射,但这种定义肯定不是自然的。

int到complex的转型

这是一个很明显的映射:从整数x映射到复数x+(0)(),并且,用户大多数都希望整数可以默认地转化为复数。因此,这个转型是敏感的。

rational到double的转型

将一个rational对象映射到一个double对象,这个double对象储存的值是rational对象的分子除以分母得到的近似值,整个过程将可能损失一定的精度。尽管这个转型有时可以被认为是自然的,但是用户往往不想让这种转型默认地进行(因为精度可能损失)。因此,这种转型不是敏感的。

double到rational的转型

如果我们认为double描述的是实数集合,那么从double到rational将没有自然的映射;但实际上,每个double对象表述的是一个有限小数,并且有限小数和有理数有着很好的自然映射,因此,这个转型有可能是敏感的。

complex到rational的转型

由于不存在用户希望发生的、从complex到rational的自然映射,所以这个转型不是敏感的。

rational到complex的转型

这个转型是比较复杂的。每一个有理数同时也可以是一个复数,因此我们可以认为存在一个从rational到complex的映射。然而,如果complex只能以x+(y)()的形式来表示复数,其中x和y都是double类型,那么这个转型就不是敏感的了,原因和rational到double的转型不是敏感的原因相同(即精度损失)。

从上面这些例子可以看出,经过了详细的分析,很多隐式转型都不是敏感的,因此,我们就不应该提供这类转型操作。练习2.6要求读者判断剩余转型的敏感性。

注意,对于某些非敏感的隐式转型,如果我们的程序确实需要它所实现的功能,那么我们可以把这种转型实现为显式转型。例如,尽管从rational到double的隐式转型不是敏感的,并且会有精度的损失,但用户却可以显式地(explicitly)将rational对象转型为double对象;我们可以在数学程序库里提供这个函数:

2.7.3 不敏感转型

回想一下2.4.1节的pool类,如2.4.1节所述,pool的构造函数需要包含一个大小参数:

此外,我们也没有给这个函数传递其他的参数。因此,我们会得到一个单参数的构造函数,并由它来创建一个转型函数。遗憾的是,从size_t到pool的转型并不是敏感的—几乎没有用户会把一个size_t对象默认地转化为一个pool对象:

我们可以用两种方法来解决上面这个问题。第一种,我们可以只提供构造函数,把避免隐式转型的工作留给用户完成;第二种,我们可以定义一个中间类:

因为对函数的实参,如果它的转型是用户定义的,那么c++是不能(隐式地)执行多于一次的转型的;这个设计将会导致编译器拒绝编译下面的错误代码:

然而,定义一个中间类有很大的缺点:它使pool类的理解和使用更加困难。现在如果想要构造一个pool对象,用户应该这样编码:

大多数用户都会倾向于使用虽容易产生错误,但更加简单的接口;因此,程序库有时也提供不敏感的(nonsensible)转型。

我们可以这样来定义一个类型的转型数目(fanout):它就是这个类型可以隐式转换为其他类型的数目。很大的转型数目往往不是我们所期望的,因为它很容易导致二义性的发生。例如,假设类from可以转型为两种类型:to和another_to,那么下面的函数调用就存在二义性:

增加一个强制转型(cast)就可以解决这个二义性问题:

然而,如果可能的话,c++程序库不应该强迫它的用户在他们的代码中使用强制转型(cast)。因此,c++程序库应该避免大的转型数目。幸运的是,只提供敏感转型的程序库一般只具有小的转型数目,特殊情况就是一些诸如int和char 的内建类型;因为很多类定义了具有这种类型单参数的构造函数,于是int和char 就具有很大的转型数目。为了避免二义性问题,程序库用户无论在什么时候,都应该避免依赖从内建类型到程序库定义类型之间的隐式转型。

本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。

继续阅读