条款26: 当心潜在的二义性
class B; // 对类B提前声明 // class A { public: A(const B&); // 可以从B构造而来的类A }; class B { public: operator A() const; // 可以从A转换而来的类B };
void f(const A&); B b; f(b); // 错误!——二义
一看到对f的调用,编译器就知道它必须产生一个类型A的对象,即使它手上拿着的是一个类型B的对象。有两种都很好的方法来实现(见条款M5)。一种方法是调用类A的构造函数,它以b为参数构造一个新的A的对象。另一种方法是调用类B里自定义的转换运算符,它将b转换成一个A的对象。因为这两个途径都一样可行,编译器拒绝从他们中选择一个。
类型转换函数的作用是将一个类的对象转换成另一类型的数据。如果已声明了一个Complex类,可以在Complex类中这样定义类型转换函数:
operator double( )
{
return real;
}
函数返回double型变量real的值。它的作用是将一个Complex类对象转换为一个double型数据,其值是Complex类中的数据成员real的值。请注意,函数名是operator double,这点是和运算符重载时的规律一致的。
类型转换函数的一般形式为:
operator 类型名( )
{
实现转换的语句
}
在函数名前面不能指定函数类型,函数没有参数。其返回值的类型是由函数名中指定的类型名来确定的。类型转换函数只能作为成员函数,因为转换的主体是本类的对象。不能作为友元函数或普通函数。
另一种类似的二义的形式源于C++语言的标准转换——甚至没有涉及到类:
void f(int); void f(char); double d = 6.02; f(d); // 错误!——二义
d是该转换成int还是char呢?两种转换都可行,所以编译器干脆不去做结论。幸运的是,可以通过显式类型转换来解决这个问题:
f(static_cast<int>(d)); // 正确, 调用f(int) f(static_cast<char>(d)); // 正确, 调用f(char)
多继承(见条款43)充满了潜在二义性的可能。最常发生的一种情况是当一个派生类从多个基类继承了相同的成员名时:
class Base1 { public: int doIt(); }; class Base2 { public: void doIt(); }; class Derived: public Base1 // Derived没有声明 public Base2 { // 一个叫做doIt的函数 ... }; Derived d; d.doIt(); // 错误!——二义
当类Derived继承两个具有相同名字的函数时,C++没有认为它有错,此时二义只是潜在的。然而,对doIt的调用迫使编译器面对这个现实,除非显式地通过指明函数所需要的基类来消除二义,函数调用就会出错:
d.Base1::doIt(); // 正确, 调用Base1::doIt d.Base2::doIt(); // 正确, 调用Base2::doIt
class Base1 { ... }; // 同上 class Base2 { private: void doIt(); // 此函数现在为private }; class Derived: public Base1, public Base2 { ... }; // 同上 Derived d; int i = d.doIt(); // 错误! — 还是二义!
对doIt的调用还是具有二义性,即使只有Base1中的函数可以被访问。另外,只有Base1::doIt返回的值可以用于初始化一个int这一事实也与之无关——调用还是具有二义性。如果想成功地调用,就必须指明想要的是哪个类的doIt。(名字查找)
参考:
http://see.xidian.edu.cn/cpp/biancheng/view/222.html