Symbian OS 中的Class命名约定

Symbian OS 中的Class命名约定
Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch——这是世界上最长的地名,位于British群岛地区,起这么长的地名的目的在于吸引游客。
Symbian OS定义了若干不同的class类型,每一种类型都具有不同的特性。Class的分类用于描述每种class对象的主要属性和行为,例如对象分配的位置 (heap或stack),特别是它们的清除方式。每个class类型都有一系列规则,明确了如何创建和销毁该类的实例。
为了便于class类型 的识别,Symbian OS使用了一个简单的命名约定,即在class名称前加入前缀字母(通常为T、C、R或M)。虽然命名约定并不总是受到青睐,但是Symbian OS中这个命名约定非常容易理解,而且作用也很明显,它可以更便于你对class的行为,特别是对清除方式进行识别。对于一个class设计者而 言,class分类简化了问题复杂性。可以在Symbian OS中依据class的功能需求选择一个与之相对应的内置基本类型。选择合适的类型之后,就可以集中精力处理class的职能。同时,在使用一个并不熟知 的class时,命名约定可以指点你如何进行一个对象的实例化,以及在确保发生意外情况时的安全的前提下使用,销毁对象的方式。
1.1 基本类型
本 章我们将对每个class类型的主要特征进行阐述,不过,在开始之前让我们先回过头来了解一下基本类型的基础知识。Symbian OS采用typedefs的形式定义一系列内建类型,从而保证这些类型独立于编译器。在Symbian OS编程中应该使用它们来取代ANSI C++中的原始类型(native type)。
 TIntX和TUintX(X可以为8,16,32)分别表示8位、16位和32位有符号或 无符号整数。如果没有特殊原因,比如应用程序的尺寸优化或兼容性方面的问题,否则在一般情况下应该使用可以在所有32位整型中通用的非特定(non- specific)的TInt或TUint类型。
 TInt64 Symbian OS在8.0以前不提供对64位算术ARM的支持,因此TInt64是通过两个32位值来实现的。从Symbian 8.0起,TInt64和TUInt64将开始直接提供对64位的支持。
 TReal32和TReal64(TReal等价于TReal64)为单精度和双精度浮点数提供支持,分别等价于float和double [1] 。Symbian OS中的浮点数计算要大大慢于整数,所以在不必要的情况下应尽量避免使用浮点数。
 TTextX (X可以为8,16),窄字符串,分别相当于8位和16位无符号整数。
 通 过 typedef’d ”类型的定义,TAny*被有效替换成指向任意类型的指针,所以TAny*可以用在Void*的场合。TAny因而等价于void,但在Symbian OS中void仍然意为“空”,所以不要想当然的用TAny来替换原始类型void。因此,在Symbian OS中一个获取void*指针(指向任意对象)且返回类型为void(空)的函数的典型格式如下:
void TypicalFunction(TAny* aPointerParameter);
这是Symbian OS typedef替换原始类型的一个例外,因为void在表达“空”的含义时具有较好的编译器独立性
 TBool bool类型,因为一些历史原因TBool和int是等价的,相应的ETrue(=1),EFalse(=0)。记住,C++会把任何非零值视为true。基于这一原因,应避免TBool类型和ETrue直接进行比较。
 每 一个TBool需要32位,对于一个bool值而言这是一个十分浪费的内存开销。因此,可以考虑使用位元组合(bitfields)来代替TBool来存 储类中大量的bool数据。而TBool类型的32位可以用位元组合的形式保存32个bool值。当然这也意味着代码复杂性的提高,因而也就需要在提高代 码复杂性和使用位元组合之间作一个权衡。
 
1.2 T类
1注意这些是typedefs,不要把它们与Symbian OS中的TRealX类型相混淆,TRealX为64位扩展精度实数。

T类的功能非常类似于ANSI C++的内部数据类型,因此它们的前缀与上面所说的typedefs为同一个字母(“T”为“Type”)。和所有的内部数据类型一样,它们也没有析构函数,所以T类也不应包含任何有析构函数的成员数据。T类中可以包含的数据成员如下:
  “‘plain ol’ data”(内部类型)及对其它T类对象。
 指针和引用(reference) “uses a”关系的效率要优于“has a”关系,后者暗示了拥有关系,TPtrC描述符是一个典型的指针T类,在第5章中有详细的论述。
T类内部包含它们的所有数据但并不包含指针,引用或句柄 (尽管允许使用指向其他对象数据的引用)。T类不允许拥有外部数据的原因是因为T类禁止拥有析构函数。
不 用析构函数,T类对象可以在stack上创建并在离开函数作用域时恰当的清除,正常返回或者产生一个leave(“leaving”的详细论述参见第2 章)。即使T类有析构函数,Symbian OS也不能在发生leave时调用它,因为leave不同于ANSI C++中的throw机制。如果需要调用析构函数来安全的清除对象,对象只能在代码作用域内的stack上创建,因为在这个作用域内不会产生leave ——这无疑带来了一些限制。
T类对象也可以在heap上创建。但在heap上创建的T类对象必须在有可能产生leave的代码之前将其压入清洁栈(cleanup stack)。一旦leave发生,T类对象的内存可以通过清洁栈来释放(详细论述参见第3章) 但是这样不会调用析构函数。
T类在一般情况下同样不定义默认构造函数,实际上,当一个T类仅由内部类型组成时,构造函数将会阻止你使用类似下面的成员初始化方式:
TMyPODClass local = {2000, 2001, 2003};
但是,T类在导出虚函数极少的情况下,默认的构造函数必须导出,因为它需要依靠一些客户端代码链接。针对这一问题我们将在第20章详细讨论EXPORT_C操作符。
通 常,T类成员非常单一,足以进行逐位复制(bitwise copy),所以复制构造函数和赋值操作符是很简单的,编译器自动产生的版本可以有更高的效率。所以除非有特殊要求通常不需要编写复制构造函数和赋值操作 符。当然如果需要防止复制操作,就需要在class中将复制构造函数和赋值操作符声明为private并且不予实现。
一些T类的API非常复杂, 例如那些可以进行字符串分析的TLex类和描述符基类TDesC与TDes(参见第5章)。但在有些情况下,T类只是一个C语言风格的由公共数据成员组成 的struct(通常,struct的前缀是S而不是T,但近来大多数Symbian OS的代码更倾向于使用T前缀)。
在枚举中你也可以看到T前缀的使用,因为它们同样是简单类型。例如:
enum TMonthsOfYear{EJanuary=1,EFebruary = 2,……,EDecember= 12};

 
1.3 C类
C前缀[1]的类皆由CBase类(定义于e32base.h)派生(直接或间接)。CBase类通过继承确保了所有的C类都具有如下两个特征。
首先,CBase有一个虚析构函数,所以C类对象都应该通过删除CBase的指针进行销毁。通常清洁栈就使用这一方法,在将C类对象压入清洁栈时需要重载调用CCleanupStack::PushL(CBase* aPtr)函数。
如 果对对象调用CCleanupStack::PopAndDestroy()(或在发生leave时),对象会通过删除CBase指针被删除。CBase 的虚析构函数确保了对派生类的析构函数的正序调用(由最底层派生类起,逐层向上调用)。所以应当认识到,C类在这一点上有别于T类,它们通常都有一个析构 函数。
还有一点需要注意的是,如果需要将非CBase的派生类压入清洁栈,将重载 CCleanupStack::PushL(TAny*aPtr)函数而不是CCleanupStack::PushL(CBase* aPtr)。正象上面说的那样,当调用PopAndDestroy()或发生leave时,将会释放对象的内存但并不会调用对象的析构函数。所以如果不直 接或间接地继承CBase类,即使你的基类有一个虚析构函数,你的类的对象也不会象你所期待
1这里可能让你觉得诧异,'C'表示'Class','C class'多少让人觉得有点罗嗦,但以T类的"Type'作为参照系,'C class'是一个正确的表示方法。
的那样可以顺利清除。
CBase 类及其派生类的第二个特征,是当首次在heap上建立对象时,将重载new操作符来对对象进行零初始化。这意味着C类对象的所有数据成员在首次创建时皆为 零。而不必由你亲自在构造函数中显式地进行这项工作。因为stack的分配不使用new操作符,所以零初始化也就不会在stack对象上发生作用。这间接 的导致了基于heap的零初始化和基于stack的非零始化的不同行为。由于这一原因,特别就leave发生时的清洁处理而言,C类对象必须在heap上 进行分配。
显而易见,基于heap的对象在失去使用价值后必须销毁。C类对象通常作为另一个类的指针成员或局部指针变量使用。如果C类对象是一个 成员变量,则应在C类的所有者的析构函数中使用delete操作来销毁。如果是一个临时性的局部指针变量,那么必须在任何可能产生leave的代码之前将 其压入清洁栈——否则一旦发生leave就会导致内存泄露。第3章将详细论述这一问题。
如果观察一下e32base.h,你将注意到CBase声 明了一个private的复制构造函数和赋值运算符。这是一个较常用的策略,可以防止用户意外地C类对象进行浅表复制(shallow copy)。如果一定要对你的类进行复制操作,那么必须显式声明并定义一个public复制构造函数和赋值运算符,因为在基类中复制构造函数和赋值运算符 被声明为private,所以不能进行隐式调用。但是,一个深层复制(deep copy)有可能导致潜在的leave发生,而就C类的本性而言,你又决不能允许在一个构造(或析构)函数中产生leave(参见第4章)。所以如果需要 为C类提供复制操作,就不要定义并实现一个公共的复制构造函数,而应加入一个允许leave的函数,例如CloneL()或CopyL(),这样既遵守了 C类的规则同时又完成了复制操作的任务。
因为大多数C类往往不足以直接提供逐位复制,所以最好避免隐式复制,这是派生于CBase的另外一个优点。CBase类中的复制构造函数和赋值操作符的private声明意味着你可以不必在每个C类中都亲自声明它们,以防止这种具有潜在危险的浅表复制。
 
1.4 R类
前 缀“R”意为“资源(Resource)”,R类通常是指向一个外部资源的句柄,例如一个文件服务会话。和C类不同的是,这里没有相应的RBase类,所 以一个典型的R类通常有一个构造函数来设定它的资源句柄为零,并显示当前资源为空。不要企图在构造函数中初始化资源句柄,因为这样可能会失败,而构造函数 中是禁止产生leave的(参见第4章)。
可以使用R类提供的函数获取R类对象提供的资源,诸如Open(),Create()或 Initialize()等函数来分配资源和设定句柄成员变量,如果调用失败则会返回一个错误码或导致leave。R类同样提供相应的Close()或 Reset()函数来释放资源和重置句柄值。在同一对象上多次调用此类函数是安全的。尽管理论上清理函数可以任意命名,但绝大多数情况下都命名为 Close()。
在使用R类时一个较常见的失误是忘记调用Close()或以为R类对象可以在析构函数中释放自己的资源。但这会导致严重的内存泄露。
R类通常占用空间较小,通常除了资源句柄以外并不包含其它数据成员。R类极少有析构函数——因为清除工作通常在Close()函数中完成。
R 类可以以一个类成员或自动变量的方式在stack上存在,有些时候也可以在heap上分配。你必须确保R类在leave发生时资源可以被有效释放,通常使 用清洁栈来处理这类情况,详细描述参见第3章。记住,如果基于heap分配一个R类自动变量,你必须在使用后确信将变量所使用资源和内存释放完毕,通常有 两种push调用完成这样的功能:CleanupClosePushL()(或者同类函数),用以确保资源的释放,也可调用标准的 CleanupStack::PushL(TAny*)函数,这个函数只是在heap单元上简单的调用User::Free()。
通常R类的成员 数据要简单的多,可以直接进行逐位复制,所以你不必期待可以在R类中看到复制构造函数和赋值操作符,否则浅表复制会引起未定义行为(undefine behavior)。比如通过逐位复制方式获得的句柄副本将会导致资源所有权的混淆。这种情况下的未定义行为多半可以归咎于两个副本都试图释放资源而导致 的。在同一对象上重复调用Close()是安全的,这是因为句柄值成员在调用Close()时复位。但是如果通过指向同一资源的两个不同的句柄对象来调用 Close()函数,这时的情况就完全不同了。如果一个句柄对象释放了指向的资源,那么指向该资源的另一个句柄也将会随即失效。
如果你的类包含一个指向不可通过逐位复制进行安全共享的资源的句柄成员,则应该声明一个复制函数来完成必要的资源复制任务。如果你希望预防对R类任何形式的复制,可以模仿C类的方式,将复制构造函数和赋值操作符声明为private,但是不予实现。
R类的规则要多于C类和T类,所以你可以看到更多不同“物种”的R类。在Symbian OS中R类所拥有资源类型是多种多样的——从文件服务会话的class RFs到基于heap分配内存的class RArray。
 
1.5  M类
计 算机界的民间传说在谈到“mix-ins”的起源时,普遍认为这一概念最早源于Symbolic's Flavors——一个早期的面向对象编程系统。它的设计者显然从“史蒂夫的冰淇淋客厅”(麻省理工学院的学生们特别喜欢的一个冰淇店)中获得了灵感,顾 客们在那里选择不同冰淇淋的风味(香草,草莓,巧克力等等)然后再加入一些混合物(坚果, 奶油软糖, 巧克力碎屑等等)。
当谈到多重继承时,这就意味着在基础的“风味”类中加入“mix-in”类来扩展它的功能。我相信这就是Symbian的设计者使用mixin这一术语并且把M设为前缀的原因,尽管在Symbian OS中的多重继承和mixin的使用要比冰淇淋店中要复杂得多。
总 体而言,M类是一个抽象接口类。一个具体类(concrete class)通常把基础的“风味”类(例如CBase或CBase派生类)作为它的第一个基类,然后再加入一个或一个以上的M类“mixin”接口来实现 接口函数。在Symbian OS中,M类通常定义为回调(callback)接口或监听器(observer)接口。
一个M类可以由其它M类继 承得来,下面我给出了两个例子。第一个展示了一个实现接口函数的具体类派生至CBase和一个mixin类—— MDomesticAnimal,MdomesticAnimal类派生于MAnimal类但并没有对它进行实现,实际上,它是MAnimal接口的一个 特化(specialization)。
class MAnimal
{
public:
virtual void EatL() =0;
};
class MDomesticAnimal : public MAnimal
{
public:
virtual void NameL() =0;
};
class CCat : public CBase,public MDomesticAnimal
{
public:
virtual void EatL(); // 通过MDomesticAnimal继承自MAnimal
virtual void NameL(); // 继承自MDomesticAnimal
…………………………
};
第二个示例展示了CBase的派生类和两个mixin接口,MRadio和MClock。在这种情况下,MClock并不是MRadio的特化,CClockRadio这个具体类分别继承并实现了这两个接口。对于M类继承而言,这种复杂组合的多重继承是可以实现的。
class MRadio
{
public:
virtual void TuneL() =0;
};
class MClock
{
public:
virtual void CurrentTimeL(TTime& aTime) =0;
};
class CClockRadio : public CBase, public MRadio, public MClock
{
public:
virtual void TuneL();
virtual void CurrentTimeL(TTime& aTime);
…………………………
};
上 面展示了多重接口继承的使用,这也是在Symbian OS中唯一鼓励使用的一种多重继承形式。其它形式的多重继承将会产生相当复杂的继承分级,并且标准基类也没有考虑这方面的设计问题。因而,对两个 CBase派生类进行多重继承将会在编译时引起二义性问题,只能通过虚拟继承来解决这一问题:
class CClass1 : public CBase
{…………………………};
class CClass2 : public CBase
{…………………………};
class CDerived : public CClass1,public CClass2
{…………………………};
void TestMultipleInheritance()
{
// 无法编译, CDerived::new 有二义性
// 应明确定义CBase::new继承自CClass1还是CClass2?
CDerived* derived = new (ELeave) CDerived();
}
让我们来考虑一下M类的特性,它可能被认为等价于java语言中的接口。和java接口一样,M类没有数据成员,所以M类对象不需要进行初始化,因而也就不必为它编写构造函数。
自 然而然的,你也会谨慎的考虑M类是否应该有析构函数(无论是虚的还是非虚的)。M类的析构函数给它的接口实现方式增加了限制条件,使得它必须在CBase 的派生类中实现。这是因为析构函数就意味着调用delete,因而就要求对象必须基于heap而不能基于stack建立。这就是说实现类一定派生于 CBase,就是因为这一原因,所有的T类和绝大部分的R类都不存在析构函数。
在通过一个指向接口类的指针拥有一个M类对象时,需要提供一些销毁M类对象的方法。如果你确信能够保证你的接口类的实施类一定派生于CBase类,那你可以在M类中提供一个虚析构函数。这样就可以让M类的所有者通过调用delete来删除它。
如 果不定义虚析构函数,对一个M类指针进行delete操作将会引发USER 42 panic,这是因为:派生于M类的具体类必然也派生于其它类,如CBase或CBase的派生类,mixin指针应在继承声明次序中排在C类之后 [1],并且这个M类的指针会转而指向M类的子对象,而不是指向有效主对象heap单元的起始位置,这个M类的子对象是用于访问分配到heap单元上的对 象的。
可以使用内存管理代码来释放heap空间,User::Free(),但如果待清除单元的定位错误将会引发panic。这一问题可以通过使用虚析构函数来解决。
但是,定义M类接口是并不是惟一的解决方案,你也可以考虑定义一个派生于CBase的抽象基类来代替M类。因为CBase恰好已经提供了一个虚析
1 具体类定义格式为
class CCat : public CBase, public MDomesticAnimal{…};
而不是
class CCat : public MDomesticAnimal, public CBase{…};
“风味”C类必须在基类列表的第一位,以此强调继承树的次序。类似这样的C类对象也可以通过重载CleanupStack::PushL()来压入清洁栈(参见第3章)
构函数,并且接口类的实施类也仅限于在C类中实现,所以这也是一个理想的办法。当然,在这一方法中,实施类将仅限于单重继承(single inheritance),就象在上面所说的那样,对CBase的多重继承将会引起二义性和可怕的“菱形”继承结构。
总 体而言,一个mixin接口类不应关心ownership的实现细节。如果调用者有可能通过指向M类接口的指针拥有一个对象,正如上面所说的那样,你就必 须确保提供一个在M类的所有者在不再需要M类时删除它的方法。但是,这并不限于通过析构函数进行清除。你也可以提供一个纯虚Release()函数来代替 析构函数,然后类的所有者就可以说“搞定了”——由实现代码决定使用何种清除方式(例如一个C类,就可以调用“delete this”来完成)。这是一个更灵活的接口——实施类基于stack或heap实现都可以,它可以实现引用计数,专门的清除或其它等等的功能。顺便说一 句,它实质上并不能调用清除函数Release()或Close(),但它可以帮助你的调用者。首先,它是可识别的并足以让你推测出它将要干什么。更重要 的是,它能够让用户使用CleanupReleasePushL()函数(参见第3章)。
类似于java的接口,一个M类通常仅有纯虚函数。但 是,也有例外允许使用非纯虚函数。比如说:当某个接口的所有继承类都具有通用的缺省行为时,给这个接口添加共享的实现就可以减少代码的冗余和维护等令人头 痛的事情。当然,这个缺省的通用实现功能有很多限制,因为mixin类不能有数据成员。所以通常情况下,所有的虚函数都是通过调用mixin接口的纯虚函 数实现功能的。
 
1.6 静态类
我们已经学完了所有有命名前缀的类,但在Symbian OS中通常还可以发现许多没有前缀字母的类,它们通过一组静态成员函数提供了附加功能。例如,User和Mem,这些类本身不可实例化;因而必须使用作用域运算符来调用它们的函数:
User::After(1000); // 当前线程挂起1000微秒
Mem::FillZ(&targetData,12); // 由&targetData起进行12位零填充

 
1.7 注意
这是一套不错的Symbian OS编程规则(尽管一切规则都有例外)。在Symbian OS中,没有违反命名规则的情况出现,但是在Symbian 系统代码本身有不符合命名规范的情况。在Symbian OS中有少部分class不符合这一命名约定。在本书中详细说明的两个例外是内核端驱动和heap描述符(HBufC),我们将在第5章详细论述。
但 例外并不是说代码中存在错误——例外必然是由特定的原因造成的。在底层代码中,你会发现一些特殊情况,比如有一些代码是在命名规则完全制定之前编写的,或 者由于效率的原因,一些class有着不同的特性和行为。所以只要当你接触一个新的class时,就有必要和本章中描述的规则进行一下对比,如果有不符合 的地方,那么就思考一下不符合的原因。从而可以将一组优秀的异常加入到你个人的漂亮的Symbian OS技巧清单当中,同时也可以避免使用不必要的违反规则的任何代码——从他人的错误当中汲取经验。
1.8 小结
本章阐释了在进行 Symbian编程中使用的主要的class类型及其特征,例如class对象构造和析构时的一些特殊要求,包括基于stack或heap的分配方式以及 class中通常包含的成员数据等问题。本章特别讨论了在发生leave时不同的class命名前缀表示的相应清除特性。
在编写class时应将 本章中的指导原则贯彻使用——仅就重写时间的减少而言,它们就可以帮助你减少编码和测试时的工作量。如果尽可能地遵守Symbian OS的命名约定,将更有助于你的客户了解class的构造,使用和销毁的方式。这自然对他们有好处,但同时也可以帮助你减少文档和未来的技术支持时间。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值