关键词所带来的差异


关键词所带来的差异

如果不是为了努力维护与C之间的兼容性,C++远可以比现在更简单些。举个例子,如果没有8种整数需要支持的话,overloaded function的解决方式将会简单得多。同样道理,如果C++丢掉C的声明语法,就不需要花脑筋去判断下面这一行其实是pf的一个函数调用操作(invocation)而不是其声明:

 

// 不知道下面是个 declaration 还是 invocation

// 直到看到整数常量 1024 才能决定

int ( *pf )( 1024 );

 

而在下面这个声明中,像上面那样的向前预览(lookahead甚至起不了作用:

 

// meta-language rule :

// pq 的一个 declaration,而不是 invocation

int ( *pq )( );

 

当语言无法区分那是一个声明还是一个表达式(expression)时,我们需要一个超越语言范围的规则,而该规则会将上述式子判断为一个声明

同样地,如果C++并不需要支持C原有的struct,那么class的观念可以借由关键词class来支持。但绝对令你惊讶的是,从C迁徙到C++,除了效率,另一个最常被程序员询问的问题就是:什么时候一个人应该在C++程序中以struct取代class

如果是1986年,我的答案毫不拖泥带水:绝不!在我的C++ Primer第一版和第二版中(译注:第三版已于19985月出版),关键词struct并未出现在书籍本文,只出现在附录C中,而附录C用来讨论C语言。那时候,这是一个人非万不得已不会指出的一些小小哲学问题中的一个。然而如果能够指出这个问题,你可以获得一些小小(一般公认非常小)的满足。通常问题会被这样指出:嘿,你知道吗,struct那个关键词,其实没什么用……”。但就像贝尔实验室(译注Bell Lab.C++发源地)的一位同事婉转对我说的,即使是最小的哲学问题也有人需要解答。如果一个C程序员渴望学习C++,当他发现我的书中没有提到struct,一定会相当苦恼。很显然把这个主题含入,可以提供语言移转时的救生索,让程序员攀上高峰时少点折磨。呵,多么哲学啊!

关键词的困扰

那么,让我重新问一次:什么时候一个人应该使用struct取代class答案之一是:当它让一个人感觉比较好的时候。

虽然这个答案并没有达到高技术水平,但它的确指出了一个重要的特性:关键词struct本身并不一定要象征其后随之声明的任何东西。我们可以使用struct代替class,但仍然声明publicprotectedprivate等等存取区段与一个完全public的接口,以及virtual functions和单一继承、多重继承、虚拟继承……早期,似乎每个人都得在一小时的C++简介中花费整整10分钟看清楚以下两者的相同:

 

class cplus_plus_keyword {

public:

   // mumble ...

};

 

和其C对等品:

 

struct c_keyword {

   // the same mumble

};

 

当人们以及在教科书中说到struct,他们的意思是一个数据集合体,没有private data,也没有data的相应操作(译注:指member function)。亦即纯然的C用法。这种用途应该和C++使用者自定义类型user-defined type)用法区别开来。在C这一边,这个关键词的设计理由因其用法而存在;而在C++那一边,选择structclass作为关键词,并用以导入ADT”的理由,是希望从此比较健全。这远比讨论函数需不需要一个大括号,或是要不要在变量名称和类型名称中使用下画线(例如IsRightis_right更具精神层次。

C所支持的structC++所支持的class之间,有一个观念上的重要差异。我的重点很简单:关键词本身并不提供这种差异。也就是说,如果一个人拥有下面的C++使用者自定义类型,他可以说喔,那是一个class”

 

// struct 名称(或 class 名称)暂时省略

{

public:

   operator int()

   virtual void foo();

   // ...

protected:

   static int object_count;

   // mumble

};

 

事实上你可以说上面那东西是个struct,也可以说它是个class。这两种声明的观念上的意义取决于对声明本身的检验。

个例子,在cfront(译注:第一个C++实现品,由Lippman完成)之中,上述两个关键词在语意分析器(parser)中是以共享的“AGGR”替换的。而在Foundation项目中,Rob MurrayALF层次结构保留了程序员真正使用的关键词。然而这份信息并未在更内层的编译器中被使用,倒是可以被一个“unparser”工具用来还原程序的ASCII面貌。啊,是的,如果程序经过“unparser”工具处理过后,无法还原原本使用的关键词,程序员一定会很郁闷即使程序在其他方面是相等的。

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

         

 

 

 

 

// 不合法吗?不,只不过是不一致罢了

class node;

...

struct node { ... };

 

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

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

 

// 不合法吗?是的。

// 以下两个声明造成矛盾的存储空间

static int foo;

...

extern int foo;

 

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

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

 

// 最初始被标示为不合法的

template < struct Type >

struct mumble { ... };

 

然而下面的代码却是合法的:

 

 

// 没问题:它显式使用了class关键词

template < class Type >

struct mumble { ... };

 

为什么?他问道。

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

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

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

策略性正确的structThe Politically Correct Struct

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

 

 

struct mumble {

   /* stuff */

   char pc[ 1 ];

};

 

// 从文件或标准输入装置中取得一个字符串,

// 然后为 struct 本身和该字符串配置足够的内存

 

struct mumble *pmumb1 = ( struct mumble* )

   malloc( sizeof( struct mumble ) + strlen( string ) + 1 );

 

strcpy( &mumble.pc, string );

 

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

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

n 从另一个class派生而来;

n 定义了一个或多个virtual functions

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

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

 

 

class stumble {

public:

   // operations ...

protected:

   // protected stuff

private:

   /* private stuff */

   char pc[ 1 ];

};

 

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

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

 

struct C_point { ... };

class Point : public C_point { ... };

 

于是CC++两种用法都可获得支持:

 

extern void draw_line( Point, Point );

extern "C" void draw_rect( C_point, C_point );

 

draw_line( Point( 0, 0 ), Point( 100, 100 ));

draw_rect( Point( 0, 0 ), Point( 100, 100 ));

这种习惯用法现已不再被推荐,因为某些编译器(如Microsoft C++)在支持virtual function的机制中对于class的继承布局做了一些变化(请看3.4节的讨论)。组合(composition),而非继承,才是把CC++结合在一起的唯一可行方法(conversion 运算符提供了一个十分便利的萃取方法):

 

struct C_point { ... };

 

class Point {

public:

   operator C_point() { return _c_point; }

   // ...

private:

   C_point _c_point;

   // ...

 

C structC++中的一个合理用途,是当你要传递一个复杂的class object的全部或部分到某个C函数去时,struct声明可以将数据封装起来,并保证拥有与C兼容的空间布局。然而这项保证只在组合(composition)的情况下才存在。如果继承而不是组合,编译器会决定是否应该有额外的data members被安插到base struct subobject之中(再一次请你参考3.4节的讨论以及3.2a3.2b)。

 

——本段文字节选自《深度探索C++对象模型》

图书详细信息:

http://www.cnblogs.com/broadview/archive/2012/02/13/2349766.html

原文地址:https://www.cnblogs.com/broadview/p/2349787.html