每个人都有思想。有些人相信自由经济学,有些人相信来生。有些人甚至相信COBOL是一种真正的程序设计语言。C++也有一种思想:它认为潜在的二义性不是一种错误。ambiguity

这是潜在二义性的一个例子:

class B; // 对类B提前声明
//

class A {
public:
A(const B&); //
可以从B构造而来的类A
};

class B {
public:
operator A() const; // 可以从A转换而来的类B
};

这些类的声明没一点错——他们可以在相同的程序中共存而没一点问题。但是,看看下面,当把这两个类结合起来使用,在一个输入参数为A的函数里实际传进了一个B的对象,这时将会发生什么呢?

void f(const A&);

B b;

f(b); // 错误!——二义

一看到对f的调用,编译器就知道它必须产生一个类型A的对象,即使它手上拿着的是一个类型B的对象。有两种都很好的方法来实现(见条款M5)。一种方法是调用类A的构造函数,它以b为参数构造一个新的A的对象。另一种方法是调用类B里自定义的转换运算符,它将b转换成一个A的对象。因为这两个途径都一样可行,编译器拒绝从他们中选择一个。

当然,在没碰上二义的情况下,程序可以使用。这正是潜在的二义所具有的潜伏的危害性。它可以长时期地潜伏在程序里,不被发觉也不活动;一旦某一天某位不知情的程序员真的做了什么具有二义性的操作,混乱就会爆发。这导致有这样一种令人担心的可能:你发布了一个函数库,它可以在二义的情况下被调用,而你却不知道自己正在这么做。

另一种类似的二义的形式源于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。

C++中有一些最初看起来会觉得很不直观的规定,现在就是这种情况。具体来说,为什么消除“对类成员的引用所产生的二义”时不考虑访问权限呢?有一个非常好的理由,它可以归结为:改变一个类成员的访问权限不应该改变程序的含义。

比如前面那个例子,假设它考虑了访问权限。于是表达式d.doIt()决定调用Base1::doIt,因为Base2的版本不能访问。现在假设Base1的Doit版本由public改为protected,Base2的版本则由private改为public。

转瞬之间,同样的表达式d.doIt()将导致另一个完全不同的函数调用,即使调用代码和被调用函数本身都没有被修改!这很不直观,编译器甚至无法产生一个警告。可见,不是象你当初所想的那样,对多继承的成员的引用要显式地消除二义性是有道理的。

既然写程序和函数库时有这么多不同的情况会产生潜在的二义性,那么,一个好的软件开发者该怎么做呢?最根本的是,一定要时时小心它。想找出所有潜在的二义性的根源几乎是不可能的,特别是当程序员将不同的独立开发的库结合起来使用时(见条款28),但在了解了导致经常产生潜在二义性的那些情况后,你就可以在软件设计和开发中将它出现的可能性降到最低。

《Effective C++》条款26 防卫潜伏的ambiguity模棱两可的状态的更多相关文章

  1. Effective C++ -----条款26:尽可能延后变量定义式的出现时间

    尽可能延后变量定义式的出现.这样做可增加程序的清晰度并改善程序效率.

  2. effective c++ 条款26 postpone variable definition as long as possible

    因为构造和析构函数有开销,所以也许前面定义了,还没用函数就退出了. 所以比较好的方法是用到了才定义.

  3. Effective C++ 条款26

    尽可能延后变量定义式的出现时间 我们知道定义一个对象的时候有一个不争的事实,那就是分配内存.假设是我们自己定义的对象.程序运行过程中会调用类的构造函数和析构函数. 我们打个例如,假设天下雨了,你带把雨 ...

  4. EC读书笔记系列之14:条款26、27、28、29、30、31

    条款26 尽可能延后变量定义式的出现时间(Lazy evaluation) 记住: ★尽可能延后变量定义式的出现.这样做可增加程序的清晰度并改善程序效率 ----------------------- ...

  5. [More Effective C++]条款22有关返回值优化的验证结果

    (这里的验证结果是针对返回值优化的,其实和条款22本身所说的,考虑以操作符复合形式(op=)取代其独身形式(op),关系不大.书生注) 在[More Effective C++]条款22的最后,在返回 ...

  6. More Effective C++ 条款0,1

    More Effective C++ 条款0,1 条款0 关于编译器 不同的编译器支持C++的特性能力不同.有些编译器不支持bool类型,此时可用 enum bool{false, true};枚举类 ...

  7. 《more effective c++》条款26 限制类对象的个数

    问题: 如何限制类对象的个数?比如1个,10个等等. 方法(1): 将类的构造函数定义为private,那么就无法实例化这个类了.但是如何创建1个对象出来?方法有2种: 1.声明一个友元函数,那么在友 ...

  8. Effective C++:条款26:尽可能延后变量定义式的出现时间

    (一) 那么当程序的控制流到达这个变量定义时.变承受构造成本:当变量离开作用域时.便承受析构成本. string encryptPassword(const std::string& pass ...

  9. Effective C++ 条款08:别让异常逃离析构函数

    1.别让异常逃离析构函数的原因 <Effective C++>第三版中条款08建议不要在析构函数中抛出异常,原因是C++异常机制不能同时处理两个或两个以上的异常.多个异常同时存在的情况下, ...

随机推荐

  1. android开发之Bundle使用

    android开发中,我们经常需要在两个activity之间传递数据,最常用的莫过于使用intent.putXXX(),可是很多时候我们也会这样: Bundle bundle = new Bundle ...

  2. JDK自带方法实现消息摘要运算

    啊,有点小注释,懒得介绍了,就贴个代码吧,大意理解就可以了. package jdbc.pro.lin; import java.security.InvalidKeyException; impor ...

  3. Bootstrap 开关(switch)控件需要注意的问题

    远程文档地址:http://www.bootcss.com/p/bootstrap-switch/ 先上lz遇到的小坑:自古无图无真相的原则 上面代码注释掉后 就是下面这个图片效果!然后加载顺序也要注 ...

  4. 从腾讯QQgame高性能服务器集群架构看“分而治之”与“自治”等分布式架构设计原则

    转载:http://space.itpub.net/17007506/viewspace-616852 腾讯QQGame游戏同时在线的玩家数量极其庞大,为了方便组织玩家组队游戏,腾讯设置了大量游戏室( ...

  5. Android 点击事件,4种回调。

    1.  继承监听接口. 2.  xml方式 : 设置 android:onClick 3. 内部类 4. 匿名类 ------------------------------------------- ...

  6. andriod 中设置sdk升级代理服务器

    Android SDK 在线更新镜像服务器资源: 大连东软信息学院镜像服务器地址: http://mirrors.neusoft.edu.cn 端口:80 北京化工大学镜像服务器地址: IPv4: h ...

  7. Lucene初步搜索

    Lucene在创立索引后,要进行搜索查询 搜索大概需要5部, 1,读取索引. 2,查询索引. 3,匹配数据. 4,封装匹配结果. 5,获取需要的值. 语言表达能力不好,大概就是分着几部吧. /** * ...

  8. SVM技法

    PLA不管胖瘦,SVM喜欢胖的 fewer dichotomies=> small VC 演算法的VC dimension shatter 掉3个点 如果限制胖瘦,两个点都shatter不掉 喜 ...

  9. hibernate面试笔记

    Hibernate使用Java 反射机制 而不是字节码增强程序来实现透明性 如果JDBC代码写的完美,优化做好,那么JDBC效率是最高的.但是,实际开发中非常不现实,对程序员要求太高.一般情况下,hi ...

  10. JS禁止横竖屏切换,强制横竖屏显示

    js判断屏幕横竖屏: function orient() { //alert('gete'); if (window.orientation == 0 || window.orientation == ...