每个人都有思想。有些人相信自由经济学,有些人相信来生。有些人甚至相信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. 所有Mac用户都需要知道的9个实用终端命令行<转>

    转自 http://www.macx.cn/thread-2075903-1-1.html 通常情况下,只有高端用户才会经常用到终端应用.这并不意味着命令行非常难学,有的时候命令行可以轻松.快速的解决 ...

  2. RedHat7/Windows7搭建JAVA开发环境(Eclipse)

    RedHat7搭建JAVA开发环境 安装JAVA # yum install java 安装Tomcat # yum install tomcat 确认Tomcat版本 # tomcat versio ...

  3. Animating Layout Changes(展开收起)

    原文地址:https://developer.android.com/training/animation/layout.html#add (1)设置布局文件: <LinearLayout an ...

  4. css - div垂直方向滚动

    只要设置 OVERFLOW-Y:auto;OVERFLOW-X:hidden即可.

  5. oracle redo日志维护

    环境 OS:Red Hat Linux As 5 DB:10.2.0.1 1.添加日志组 alter database add logfile group 4 ('/u01/app/oracle/or ...

  6. jQuery 事件 方法

    jQuery 事件方法 事件方法触发器或添加一个函数到被选元素的事件处理程序. 下面的表格列出了所有用于处理事件的 jQuery 方法. 方法 描述 bind() 向元素添加事件处理程序 blur() ...

  7. PHP 学习笔记 (三)

    stream_context_create()函数的用法: $url = "http://www.someweb.com" $context = stream_context_cr ...

  8. Java之final的解析

    在Java中,final关键字可以用来修饰类.方法和变量(包括成员变量和局部变量). 当用final修饰一个类时,表明这个类不能被继承. 对于一个final变量,如果是基本数据类型的变量,则其数值一旦 ...

  9. 【转】ASP.NET MVC教程

    转自:http://www.cnblogs.com/QLeelulu/category/123326.html ASP.NET MVC的最佳实践与性能优化的文章 摘要: 就一些文章链接,就不多废话了. ...

  10. C++ 静态、动态链接库的简单实现

    一.什么是静态链接库,什么是动态链接库? 1.静态链接库就是你使用的.lib文件,库中的代码最后需要连接到你的可执行文件中去,所以静态连接的可执行文件一般比较大一些.在静态库情况下,函数和数据被编译进 ...