设为首页 加入收藏

TOP

1.2 关键词所带来的差异(A Keyword Distinction)(2)
2013-10-07 14:49:02 来源: 作者: 【 】 浏览:60
Tags:1.2 关键词 带来 差异 Keyword Distinction

1.2  关键词所带来的差异(A Keyword Distinction)(2)

我第一次被我所谓的"关键词受难记"绊倒,是在大约1988年,当时我们测试小组中的一位成员对cfront发出一个"大难临头,即将完蛋"的"臭虫"报告。在cfront内部的类型层次结构的原始声明中,根节点(root node)和每一个派生下来的子类型(subtype)是以struct关键词来声明的,而在陆续修改的头文件(header files)中,某些派生子类型(derived subtypes)的前置声明(forward declaration)却使用了关键词class:

  1. // 不合法吗?不,只不过是不一致罢了  
  2. class node;  
  3. ...  
  4. struct node { ... }; 

我们的测试员说这是一个粗野的错误,是一个cfront无法捕捉的问题,因为……呃……当然……cfront用来编译它自己。

真正的问题并不在于所有"使用者自定义类型"的声明是否必须使用相同的关键词,问题在于使用class或struct关键词是否可以给予"类型的内部声明"以某种承诺。也就是说,如果struct关键词的使用实现了C的数据抽象观念,而class关键词实现的是C++(www.cppentry.com)的ADT(Abstract Data Type)观念,那么当然"不一致性"是一种错误的语言用法。就好像下面这种错误,一个object被矛盾地声明为static和extern:

  1. // 不合法吗?是的。  
  2. // 以下两个声明造成矛盾的存储空间  
  3. static int foo;  
  4. ...  
  5. extern int foo;  

这组声明对于foo的存储空间造成矛盾。然而,如你所见,struct和class这两个关键词并不会造成这样的矛盾。class的真正特性是由声明的本身(declarationbody)来决定的。"一致性的用法"只不过是一种风格上的问题而已。

我第二次触撞这个题目是在C++(www.cppentry.com) 3.0所引入的"parameter lists of template"上头。Steve Burof,我的另一位贝尔实验室同事,有一天走进我的办公室并指出以下程序代码被语意分析器(parser)视为不合法:

  1. // 最初始被标示为不合法的  
  2. template < struct Type > 
  3. struct mumble { ... }; 

然而下面的代码却是合法的:
  1. // 没问题:它显式使用了class关键词  
  2. template < class Type > 
  3. struct mumble { ... }; 

"为什么?"他问道。

"为什么不?"我清楚地予以回击,然后详细说明templates并不打算与C兼容。我说让我们撇开struct不谈,然后再看看它做什么事。我想我大概一跃而过我的Sun 3/60机器并以最佳姿态挥舞鼠标--老实说我不记得了。不过我记得最终我更改了语意分析器(parser),使它同时接受两个关键词--在没有事先告知Bjarne和"少不更事"的ANSI C++(www.cppentry.com)委员会的情形下。这是这个语言用词的诞生由来。

你可能会争辩说,如果这个语言只支持一个关键词,可以省掉许多混淆与迷惑。但你要知道,如果C++(www.cppentry.com)要支持现存的C程序代码,它就不能不支持struct。好的,那么它需要引入新的关键词class吗?真的需要吗?不!但引入它的确非常令人满意,因为这个语言所引入的不只是关键词,还有它所支持的封装和继承的哲学。你不妨发挥一下想象力,想想当谈论到一个抽象的base struct(例如ZooAnimal struct层次结构)时,其中内含一个或更多virtual base struct的情形。

在前面的讨论中,我区分了"struct关键词的使用"和"一个struct声明的逻辑意义"。你也可以主张说这个关键词的使用伴随着一个public接口的声明,就好像在公开演讲中使用暗语或昵称一样。你甚至可以主张说它的用途只是为了方便C程序员迁徙至C++(www.cppentry.com)部落。

策略性正确的struct(The Politically Correct Struct)

C程序员的巧计有时候却成为C++(www.cppentry.com)程序员的陷阱。例如把单一元素的数组放在一个struct的尾端,于是每个struct objects可以拥有可变大小的数组:

  1. struct mumble {  
  2.    /* stuff */  
  3.    char pc[ 1 ];  
  4. };  
  5.  
  6. // 从文件或标准输入装置中取得一个字符串,  
  7. // 然后为 struct 本身和该字符串配置足够的内存  
  8.  
  9. struct mumble *pmumb1 = ( struct mumble* )  
  10.    malloc( sizeof( struct mumble ) + strlen( string ) + 1 );  
  11.  
  12. strcpy( &mumble.pc, string ); 

如果我们改用class来声明,而该class是:

指定多个access sections,内含数据;

从另一个class派生而来;

定义了一个或多个virtual functions。

那么或许可以顺利转化,但也许不行!

C++(www.cppentry.com)中凡处于同一个access section的数据,必定保证以其声明顺序出现在内存布局当中。然而被放置在多个access sections中的各笔数据,排列顺序就不一定了。在下面的声明中,前述的C伎俩或许可以有效运行,或许不能,需视protected data members被放在private data members的前面或后面而定(译注:放在前面才可以):

  1. class stumble {  
  2. public:  
  3.    // operations ...  
  4. protected:  
  5.    // protected stuff  
  6. private:  
  7.    /* private stuff */  
  8.    char pc[ 1 ];  
  9. }; 

同理,base classes和derived classes的data members的布局也未有谁先谁后的强制规定,因而也就不保证前述的C伎俩一定有效。Virtual functions的存在也会使前述伎俩的有效性成为一个问号。所以,最好的忠告就是:不要那么做(第3章会更详细地讨论相关的内存布局主题)。

如果一个程序员迫切需要一个相当复杂的C++(www.cppentry.com) class的某部分数据,使他拥有C声明的那种模样,那么那一部分最好抽取出来成为一个独立的struct声明。将C与C++(www.cppentry.com)(参考[KOENIG93])组合在一起的做法就是,从C struct中派生C++(www.cppentry.com)的部分:

  1. struct C_point { ... };  
  2. class Point : public C_point { ... }; 

于是C和C++(www.cppentry.com)两种用法都可获得支持:
  1. extern void draw_line( Point, Point );  
  2. extern "C" void draw_rect( C_point, C_point );  
  3.  
  4. draw_line( Point( 0, 0 ), Point( 100, 100 ));  
  5. draw_rect( Point( 0, 0 ), Point( 100, 100 )); 

这种习惯用法现已不再被推荐,因为某些编译器(如Microsoft C++(www.cppentry.com))在支持virtual function的机制中对于class的继承布局做了一些变化(请看3.4节的讨论)。组合(composition),而非继承,才是把C和C++(www.cppentry.com)结合在一起的唯一可行方法(conversion 运算符提供了一个十分便利的萃取方法):
  1. struct C_point { ... };  
  2.  
  3. class Point {  
  4. public:  
  5.    operator C_point() { return _c_point; }  
  6.    // ...  
  7. private:  
  8.    C_point _c_point;  
  9.    // ... 

C struct在C++(www.cppentry.com)中的一个合理用途,是当你要传递"一个复杂的class object的全部或部分"到某个C函数去时,struct声明可以将数据封装起来,并保证拥有与C兼容的空间布局。然而这项保证只在组合(composition)的情况下才存在。如果是"继承"而不是"组合",编译器会决定是否应该有额外的data members被安插到base struct subobject之中(再一次请你参考3.4节的讨论以及图3.2a和图3.2b)。
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇1.1 C++对象模式(The C++ Objec.. 下一篇1.2 关键词所带来的差异(A Keywo..

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: