C++之父Bjarne stroustrup曾经说过:不需要了解所有的c++细节,也能够写出好的c++程序;不应该注重语言方面的特征,而应该注重软件设计技术本身。很显然,我的这篇文章,与这两句话背道而驰:).的确,我们程序员,不应该把精力放在c++本身语言的特征上,而是应该思考软件设计技术本身。那么,在我们需要提高对c++理解的同时,是不是我们从下面几个方面为着眼点1) 从编译原理的角度2) 从技术需求的角度3) 从软件设计技术的角度从以上的几个角度,来重新审视c++一些晦涩语法,或许,我们能从中获益。在这里,我要说的是,我们不单单是要记住这些c++语言特性怎么样的使用,而是应该知道这些语言特性背后隐藏的故事,以便于我们更深层次地理解c++,理解软件设计。一、子类通过函数名字隐藏父类函数。如下例:
当我们编译pd->f(10)操作时,编译器报错。按照我们常规的理解是:父类的函数void f(int x)与子类的函数void f(double*pd),由于参数类型不同,其函数签名也是不一样的,按照这样的逻辑,在这个类继承体系中,这两个函数完全应该是互不隐藏的,我们完全可以认为是符合overloaded规则的两个函数。 但是,在c++里,子类通过函数名字隐藏父类函数,而不是通过函数签名!c++给出的解释也是合理的:试想一种情况:你使用了别人写的类库,继承其中的某个类,写了你自己的子类。如上面的例子,你的子类就是Derived,而类库中的父类就是Base.当你根本不知道在父类中还有这样一个f(int x)函数时,在调用子类Derived的f函数时,你犯了错误,参数类型传成了int类型(或者不是你犯的错误,编译器帮你自动转化为int类型),结果是:程序可以正常运行,但是,执行的结果却不是你所期望的,是f(int x)调用,而不是你自己的实现:f(double* pd)调用! 这就是c++为什么通过函数名字隐藏父类函数的原因。 说到这里,我们需要补充几句:虽然c++在语言层面上给我们提供了这样的保证,但是,子类hide父类的函数,这是一个非常不好的设计。从OO的角度出发,应该讲求的是Liskov Substitution Principle。即:suntypes must be substitutable fro their base types.很显然,当hide行为发生时,从接口的角度来讲,子类与父类是不能互为替代的。父类的protected or public的方法,应该很自然地由其所有子类所继承,而不是被隐藏。隐藏行为的发生,相当于在这套继承体系中开的一个后门。很显然,C++帮助我们自动隐藏了父类的方法,但是,作为程序开发的我们,应该意识到这一点,也应该避免这样的设计。二、c++的per-class allocator语法规则 在D&E of C++一书中,Stroustrup给出了几点c++提供per-class allocator的理由,这些理由也是我们使用class level的allocator的原因,所以,有必要我们总结一下:第一、许多程序应用,需要在运行的过程中,大量地Create和Delete对象。这些对象,诸如:tree nodes,linked list nodes,messages等等。如果在传统的heap完成这些对象的创建,销毁,由于大量的内存申请,释放,势必会造成内存碎片。这种情况下,我们需要对内存分配进行细粒度的控制。第二、一些应用需要长时间跑在内存受限的装置上,这也需要我们对内存分配进行细粒度的控制,而不是无限制地分配,释放。主要基于以上的两点,c++提供了per-class allocator语言支持。如下例:
new操作符函数负责对象X的内存分配。对这样一个语法规则,我们好奇的是,为什么声明了一个我们从来都不使用的参数size_t sz.我们的使用语法如下: X* px = new X;C++也给出了解释:per-class allocator机制将适用整个类的继承体系。例如:
对于子类Y,其内存分配函数也是X::operator new()。但是,在这里,内存分配的大小,不应该是sizeof(X),而是sizeof(Y).问题的关键在这里:C++通过提供多余的参数size_t sz,而给开发者提供了更大的灵活性,也即:per-class allocator是面向类的继承体系的内存管理机制,而不单单是面向单个类。三、Koenig Lookup机制。 大家对Andrew Koenig应该很熟悉,c++大牛,是AT&T公司Shannon实验室大规模编程研究部门中的成员,同时他也是C++标准委员会的项目编辑。他拥有超过30年的编程经验,其中有15年的C++使用经验。 Koenig Lookup,就是以Andrew Koenig命名的查找规则。在看这个定义之前,我们先弄清楚函数所在的域的分类,一般来讲,分为:1:类域(函数作为某个类的成员函数(静态或非静态))2:名字空间域3:全局域(即C++默认的namespace) 而Koenig Lookup机制,就是当编译器对无限定域的函数调用进行名字查找时,除了当前名字空间域以外,也会把函数参数类型所处的名字空间加入查找的范围。如下例:
如上的代码,使用operator<<操作符函数,打印对象的状态,但是函数ostream& operator<<(ostream& out, const MyArg& myArg) 的定义域是处于名字空间Koenig中,为什么编译器在解析main函数(全局域)里面的operator<<调用时,它能够正确定位到Koenig名字空间里面的operator<<?这是因为根据Koenig查找规则,编译器需要把参数类型MyArg所在的名字空间Koenig也加入对ostream& operator<<(ostream& out, const MyArg& myArg) 调用的名字查找范围中。 如果没有Koenig查找规则,我们就无法直接写cout<<myArg;,而是需要写类似Koenig::operator<<(std::cout, myArg); 这样的代码(使用完全限定名)。这样的结果是,即不直观也不方便。
其实在C++里,提供了很多类似于Koenig查找规则的机制,以保证程序语法上的简洁,明了。例如:许多的操作符函数,COPY构造函数。而这些,也是我们写出专业的C++程序的基本。未完待续:)
Powered by: C++博客 Copyright © 爱上龙卷风