每个人都有思想。有些人相信自由经济学,有些人相信来生。有些人甚至相信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. Java基础知识强化之网络编程笔记02:Socket通信原理图解

    1. Socket (1)Socket套接字  网络上具有唯一标识的IP地址和端口号组合在一起才能构成唯一能识别的标识符套接字 (2)Socket原理机制:  • 通信两端都有Socket.  • 网 ...

  2. Android(java)学习笔记169:Activity中的onCreate()方法分析

    1.onCreate( )方法是android应用程序中最常见的方法之一: 翻译过来就是说,onCreate()函数是在activity初始化的时候调用的,通常情况下,我们需要在onCreate()中 ...

  3. 禁止输出重定向(>)覆盖已存在文件(防止误操作)

    在输出重定向中,>表示重定向并覆盖已有文件内容,为了防止误操作,覆盖重要的内容,可以使用如下命令: set -C 这样输出重定向到一个已有文件就会提示: cannot overwrite exi ...

  4. python np.linspace

    该函数的形式为: linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None) 作用为:在规定的时间内,返回固定间隔的 ...

  5. github上建站和使用markdown写文章

    积累了那么久,终于成功搭建了github上的个人网站.虽然方法有点巧妙.不是还是建成了 同时学会用markdown写基本的文章.感觉还可以.附带我的github上的静态页面网站的网址:http://z ...

  6. 类似与三元表达式的 json 读取值

    需要先在项目中添加 json的dll json 序列里面的key在item.feeType里面必须存在 否则会报 未将对象引用到实例 myDr["feeType"] = Newto ...

  7. 【Windows】Windows中的数据类型以及命名

    一.大写标示符 Windows中的很多标识符都是以两个或者三个大写字母作为前缀的,且其后紧跟一个下划线.这些标识符都是常量数值,前缀表明该常量的一般类别.如下 前缀 常量 CS(Class Style ...

  8. Linux上安装KDE, Gnome和VNC

    安装所需环境:需要至少256m的可用内存(128也可以不过有点卡)CentOS或类似OS(Debian的话改成apt-get应该也可以) 1,安装KDE 1 yum install kdepim 或安 ...

  9. jQuery Ajax(load,post,get,ajax)用法与详解

    今天看到群里面有网友们问到Jquery Ajax的(load,post,get,ajax)之间的区别,现在整理了一篇文章出来,希望可以帮到网友们,首先我们先来看一些简单的方法, 这些方法都是对jQue ...

  10. 为什么每个浏览器都有Mozilla

    你是否好奇标识浏览器身份的User-Agent,为什么每个浏览器都有Mozilla字样? Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 ...