C++ and machine architecture
Arithmetic types
------------------
An int is usually implemented as the natural machine word size of the particular implementation. 在大多数的机器里是32位字节,在老的机器里可能是16位,在一些新的机器里甚至达到64位字节。
同样,一个指针(如void*)也通常被当作一个机器word来对待,不过有的机器里它可能更复杂。
It is assumed that the symbian platform is implemented on a machine with a 32-bit or greater machine word, and 32-bit pointers.The types TInt and TUint are typedefed onto the built-in int and unsigned int types, and are guaranteed to be at least 32 bits.
When you need a specific size, regardless of implementation, use a sized type.Serveral of these are available:
——TInt32 / TUint32
32位signed and unsigned integer
In each case, the representation is a 32-bit machine word which, in the ARM architecture, must be aligned to a four-byte boundary. The compiler ensures that this is always the case.
——TInt8 / TUint8 / TText8
8-bit signed and unsigned integer, and 8-bit character
In each case, the representation is an 8-bit byte, which has no specific alignment requirement.
——TInt16 / TUint16 / TText16
16-bit signed and unsigned integer, and 16-bit character
In each case, the representation is a 16-bit halfword, which should be aligned to a
two-byte boundary. The ARM architecture (prior to ARM8 and the Thumb instruction set) is relatively poor at handling 16-bit quantities, and their use is recommended only when space is at a premium, or for Unicode text handling.
——TInt64
64-bit unsigned ineger
Since ARM has no built-in support for 64-bit arithmetic. TInt64 is implemented as a C++ class.
——TReal / TReal64
双倍精度的浮点数,IEEE754 64-bit representation
这个是推荐使用的一般浮点数类型,ARM架构并没有提供浮点的支持,你应该尽量使用整型运算(例如,大多数GUI计算)只有点程序真的需要时(如电子表格程序)才不得不使用这些浮点类型。
——TReal32
32-bit floating point
This is smaller and quicker, but should only be used when space and /or time are at a true premium, as its precision is unsatisfactory for many application.
Compound types
----------------
Apart from classes, C++ inherits from C various other types of compounding
A struct maps an area of memory
struct TEg
{
TInt iTnt; //offset 0, 4 bytes
TText8 iText; //offset 4, 1 byte
//3 wasted bytes
TReal iReal; //offset 8, 8 bytes
} //total length = 16 bytes
//都是以4 bytes = 32 bits为一个单位的
结构被看做是T types:那是因为他们都没有自己的堆分配资源(如C type classess样)
一个数组包括很多built-ins or other types
TInt a[32]; //32 TInts, = 128 bytes
S b[3]; // 3 S's, = 48 bytes
使用C++数组的最大缺点就是没有对其索引的自动检查。基于这个理由,也为了支持更多复杂的container,C++已经开始发展STL。symbian并不支持STL,但有它自己的一套有效的解决方案。
Pointers
----------
指针存放了内存地址。
S* ps; // pointer to an S
ps = &s; //take address of existing S
通常我们需要一个指向包含任何可能内容的内存的指针,在C++中我们通常用void*指针来表示,但在symbian平台中,我们用TAny* 来代替它。
Strings
----------
在C++中,基本的字符串就是一个字符数组:
char* hello = "hello";
这段做两件事情:首先,它在内存中为'h','e','l','l','o',''分配六个字节,其次,它设定一个指针hello来指向这一内存的首地址。
在c++库中,处理字符串要顾及到其越界后可能需要的扩展,在symbian中的解决方法是使用descriptors
Functions
--------------
函数是在程序中可以任意调用执行的一段代码,而堆栈常常用来传递参数和存放局部变量,堆栈可以由机器的register而进行扩展,象ARM这种寄存器异常丰富的系统,内存都不经常被使用。But, conceptually, there is a stack, and for the purposes of this explanation it is convenient to consider the stack as if it were implemented entirely in memory.
参数通过拷贝或求值被传递到调用函数的堆栈段中,在参数中传递过多的数据是不明智的,事实上,就是任何超过2个机器字的数据量都不被提倡。因为这将引起过多的拷贝动作,相反,用一个指针或是引用来处理这些数据的地址要比传递数据本身好得多:)
在多任务的操作系统中(如symbian这样的平台),每个线程都有它自己的堆栈,这是一个预先分配的内存。使用堆栈的好处就是分配和解除分配空间是非常迅速的——只有一对指令(Push/Pop)。同样,在堆栈中的变量的生命期是很明确的:与它所属函数的生命期是一致的。
当函数返回时,它的堆栈空间仍然在那,只是没有被重新分配。当下个函数被调用时它将仍被使用。一个潜在的严重错误就是,我们在函数中创建了新的对象,最后返回一个指向它的指针:
TEg* foo()
{
TEg s;
TEg* ps = &s;
return ps; // !!error!!
}
事实上,这个指针是无效的,因为该内存空间已经不属于foo函数。里面的数据也就不确定了。
This error is so obvious that a compiler will trap it. But it can occur in more subtle forms:
foo(CContainer* aContainer)
{
TEg s;
TEg* ps=&s;
aContainer->iMember=ps;
} //真毒:)真狡猾
These cannot be trapped so easily.
Heap
------
每个线程都有个堆,你能在需要的时候在上面创建新的对象或释放对象。使用堆的好处就是,对象的生命周期完全掌握在你的手里,这也带来了职责,你在用完该对象时可别忘记了释放它,并且不能再使用一个指针指向已经被释放的对象了。
[Object orientation basics]
Relationships
---------------
在面向对象系统中,在对象之间的关系很多。以下是一些列子:
1、the control environment(CCoeEnv)有一个window server session(RWsSession)。window server session的生命周期是和control environment一样的。
2、the app UI(CEikAppUi)使用一个文档(CEikDocument),The document has independent existence: the app UI may come into being for a while, and then be destroyed, but throughout the app UI's lifetime, it uses the document.
[Object lifetimes and cleanup]
overview
----------
在堆和栈中,对象都有下列近似的生命周期:
1、在堆或栈上为对象分配内存
2、初始化:即把内存的内容设定为有用的值
3、使用该对象
4、清除:清除任何该对象所使用的资源
5、在堆或栈上释放响应内存空间
对象的生命周期是一个基本的概念:在某些操作系统中,它会被忽视,因为在那里当程序中断后堆和栈会被释放。在symbain平台上,程序是为了数月运行而不中断的。因此当他们的生命周期结束后就立即被清除是很重要的,无论他们是在堆或栈中分配的,或者是因为正常运行或错误运行而终止的。
Lifetimes in C
----------------
在C堆栈中,可能看起来如下:
#include "s.h"
void foo()
{
S s;
sInitialize(&s, p1, p2);
sUse(&s, p3, p4);
sCleanup(&s);
}
S对象的分配是在函数入口处进行的,并且在退出时释放空间。
在C堆中,应该看起来如下:
void foo(0
{
S* s = (S*)malloc(sizeof(S));
//should really check this succeded!!
sInitialize(s, p1, p2);
sUse(s, p3, p4);
sCleanup(s);
free(s);
}
这里得到的s是指向对象的一个地址了,做为参数就更加简便了:)
Mostly, the lifetime of a heap-based object would not be contained within a single function like this: it might be created from one function, used from another, and destroyed from another.
Lifetimes in C++
-------------------
C++中对象的生命周期从构造函数开始,结束于析构函数。它还拥有一个new()操作,比malloc()要更好。以及相应的delete,这也是比free()好的。
在C++的堆栈中,一个对象生命周期看起来如下:
void foo()
{
S s(p1, p2); //invokes constructor
s.Use(p3, p4); //nice syntax;
} //invokes destructor
注意,如果说出现意外情况,例如use()函数调用时失败,那函数就不能正常返回。这样析构函数就不能被正确调用。我们来看一下symbian平台是如何处理这些的:
在C++堆中,对象生命周期看起来如下:
void foo()
{
S* s = new S(p1, p2); //allocate, construct - should really check
s->Use(p3, p4);
delete s; // destruct, de-allocate
}
Again, the syntax is much nicer. Only one thing cannot be provided by C++: the user of a class must still remember to delete the object at the end of its lifetime.
Lifetimes in the Symbian platform
-----------------------------------
symbian平台中堆栈上的对象的生命周期和标准C++中的非常相似,不过对其的控制有不同,如下:
void Fool()
{
CS* s = new (ELeave) CS; //allocate and check
CleanupStack::PushL(s); //push, just in case
s->ConstructL(p1, p2); //finish constructing - might leave
s->UseL(p3, p4); //use - might leave
CleanupStack::PopAndDestroy(); //destruct, de-allocate
}
上面的代码段展示了四件大事
1、all heap-based classes have names beginning with C: they are in fact derived from a single base class, CBase, which exists solely to suport easy cleanup
2、a cleanup stack is used to hold references to objects: if a leave occurs due to out-of-memory or some other error, objects held on the cleanup stack are popped from it, and destroyed. In the case of CBase* objects pushed to the stack, they are destroyed by calling their C++ destructor. The CBase class has a virtual destructor (CBase::~CBase()) which makes this possible.
3、any function which might leave is designated by a trailing L in its name. When you see a function that might leave, you must always ask what would happen if it did leave, and what would happen if it did not. The operating system provides all the program infrastructure required to allow objects to be de-allocated even when a leave occurs, but without burdening the programmer.
4、new (ELeave) is an overloaded operator new() function, which will leave if it fails to allocate the required memory. It never returns a null pointer.
另外还有两件事情值得注意:
1、since the cleanup stack itself requires memory allocation for each stack frame, a push might leave. The PushL() function reflects this in its name. The cleanup stack is guaranteed to have a free slot before a CleanupStack::PushL(), so that the object reference will always be successfully stored on the stack. If a leave occurs when allocating the next stack frame, the object will be popped and destroyed as normal.
2、the C++ constructor must not leave. For objects whose construction requires resource allocation or any other operation that might fail, this means that construction must be separated into a C++ constructor that does not leave, and another initialisation function that might leave, which is conventionally called ConstructL().清除栈、CBase以及双重构造都是操作系统的核心。
[Multiple inheritance and interfaces]
Overview
----------
多重继承是C++的一个重要特性。
在symbian平台中多重继承只有一个简单的目的:也就是,定义接口协议。(这个我们在com定义接口的方法中也能看到,一是用多重继承二是用嵌套类,很值得研究:)它们被用于下列情况:有一个协议提供者类,和一个协议使用者。最理想的状况是协议的使用者完全不关心协议提供者的(这个在com中也是的,使用接口而不关心接口是如何实现的),当然除了那些提供特殊协议的情况。这样的例子包括:
1、一个应用程序control就是一个协议提供者,它的菜单使用了菜单observing的协议。当一个菜单选项被选中以后,菜单observing 的协议就被执行,所以应用程序control就可以处理菜单命令了。除此以外,菜单control不知道关于应用程序control的任何东西。
2、一个应用程序,如电子表格,可能有一个引擎,which provides protocols for updating and getting its model contents, and a user interface, which uses these protocols to drive the engine. The engine is written with no knowledge of the user interface, and the user interface is written with minimal knowledge of the engine. 他们之间的交互依靠引擎所提供的协议:)
要理解为什么使用接口,下面可以一一说明:
1、the traditional method which uses single inheritance
2、a technique of overcoming the disadvantages of single inheritance, using protocol intermediary classes
3、a better technique, which uses multiple inheritance with interface classes
4、the restrictions on C++ multiple inheritance in Symbian OS
Protocols using classic single inheritance
--------------------------------------------
A classical use of single inheritance is to define an abstract protocol from which derived classes may inherit. A base class defines a protocol:
class CProtocol: public CBase
{
public:
virtual void HandleEvent(TInt aEventCode) = 0;
};
这个协议只包含一个函数HandleEvent(), 这里的事件由一个整型事件代码指明。
一个具体的协议提供类是从以上基类派生过来的,它提供了基类中纯虚函数的具体处理:
class CProtocolProvider: public CProtocol
{
public:
//construct/destruct
static CProtocolProvider* NewLC();
void Destruct();
//implement the protocol
void HandleEvent(TInt aEventCode); //handle protocol
protected:
void ConstructL();
};
另外,有个协议使用者类,它并不知道CProtocolProvider的一切,但它知道CProtocol类和指定协议的那个函数。它有个函数会使用HandleEvent():
void CProtocolUser::DoSomething(CProtocol* aProtocol)
{
_LIT(KOutput1, "External system doing something ");
_LIT(KOutput2, "invoking protocol - event 3 ");
testConsole.Printf(KOutput1);
testConsole.Printf(KOutput2);
aProtocol->HandleEvent(3); //handle an event
};
在CProtocol中定义的虚函数是由CProtocolProvider提供实现的,下面是代码:
void CProtocolProvider::HandleEvent(TInt aEventCode)
{
//handle an event in the protocol user
_LIT(KOutput1, "CProtocolProvider handling event %d ");
testConsole.Printf(KOutput1, aEventCode);
}
因此,尽管协议的使用者并不知道派生类CProtocolProvider,但可以通过一个指向这个派生类的指针来访问其成员函数
void doExampleL()
{
//show use of interface with simple class
CProtocolProvider* provider = CProtocolProvider::NewLC();
CProtocolUser* user = CProtocolUser::NewL();
user->DoSomething(provider);
CleanupStack::PopAndDestroy(); //user后进先出:)
CleanupStack::PopAndDestroy(); //provider
}
在上面的情况中,provider指针被强制转换成CProtocol* base,这是CProtocolUser::DoSomething()的需要.
使用这种方法的优势在于:
它促成了协议使用者于协议提供者的无关性。
这就是我们最想追求的状态,但这个方法也有很严重的缺点:
1、它强制协议的提供者要从一个协议基类派生
2、可以,如果有多个协议要被提供,那唯一的方法就是在一个single umbrella protocol中包含所有的需要协议,这是个很糟糕的封状。首先基类会变得很庞大,并且要从这么辨认这么多成员函数以及所属协议也不是件很容易的事情。其次,it may be desirable to have another provider class which provides some of the protocols provided by the first class, and others in addition. To support this requires an even larger umbrella protocol.因此,这种方法经常导致大的基类,协议间彼此无关。
Protocols using an intermediate class
----------------------------------------
如果要克服这样的缺点,我们建议使用一个间接的协议对象,它包含一个指向协议提供者的指针。基本的协议类是一样的,如下:
class TProtocol
{
public:
virtual void HandleEvent(TInt aEventCode) = 0;
};
but there is now a derived class for use with the CProtocolProvider only:
class TProtocolProviderIntermediary: public TProtocol
{
public:
//construct
TProtocolProviderIntermediary(CProtocolProvider* aRealProvider);
//protocol itself
void HandleEvent(TInt aEventCode);
private:
CProtocolProvider* iRealProvider; //real provider
};
This class provides the protocol as far as the protocol user is concerned. The concrete implementation of HandleEvent() just passes the function call to the real protocol provider class, which has a non-virtual DoHandleEvent() to provide the required functionality:
void TProtocolProviderIntermediary::HandleEvent(TInt aEventCode)
{
iRealProvider->DoHandleEvent(aEventCode);
}
With this system, CProtocolProvider is derived, not from the protocol definition class, but from CBase
class CProtocolProvider: public CBase
{
public:
//construct / destruct
static CProtocolProvider* NewLC();
void Destruct();
//implement the protocol
void DoHandleEvent(TInt aEventCode); //handle protocol
protected:
void ConstructL();
public:
TProtocolProviderIntermediary* iProviderIntermediary;
}
TProtocolProviderIntermediary是在CProtocolProvider的构造函数中被构造的,并且被它的析构函数所释放。基于这个原因,TProtocolProviderIntermediary是一个T类:它并不拥有CProtocolProvider,and cannot be orphaned.
When a function in the protocol user requiring the protocol provider is called, it must now be called passing the intermediary object as a parameter:
LOCAL_C void doExampleL()
{
//show use of interface with simple class
CProtocolProvier* provider = CProtocolProvider::NewLC();
CProtocolUser* user = CProtocolUser::NewLC();
user->DoSomething(provider->iProviderIntermediary);
CleanupStack::PopAndDestroy(); //user
CleanupStack::PopAndDestroy(0; //provider
}
协议使用者的DoSomething()本质上是和以前一样的,除了其参数现在变成一个TProtocol*。因此,用户只要知道基类TProtocol就可以了,虚函数机制导致间接派生的HandleEvent()被调用了,这个函数实际上调用的是真正协议提供者的DoHandleEvent()。
这种方法解决了困扰single inheritance的问题:
1、现在,任何数目的协议都可以被支持了,并且可以被分别封装,每个协议都需要一个intermediary class,并且每个间接类的对象都要指向相应的实际协议提供者类的对象。
2、no large base classes are needed to provide umbrellas for servral protocols.
但是,它还是有很严重的缺点:
1、这是很笨拙的:not only does each protocol require an abstract class (which cannot be avoided), but also, at each point in the derivation tree at which a protocol is introduced, a derived protocol class must be written which implements the protocol for the relevant class which really provides the protocol: further, the derived protocol object and the real protocol provider must be linked
2、if there are many classes which use many protocols in this way, not only is the method cumbersome to program, but it is uneconomical on memory, since each derived protocol class object requires at least two machine words of heap memory. This consideration becomes more serious if there are more small real protocol providers, providing many different protocols.
Protocols using interface classes
------------------------------------
这些问题的解决可以通过多重继承,一个基类MProtocol申明了那个协议:
class MProtocol
{
public:
virtual void HandleEvent(TInt aEventCode) = 0;
};
这次协议的提供者是从CBase和MProtocol派生的:)
class CProtocolProvider: public CBase, public MProtocol
{
public:
//construct/ destruct
static CProtocolProvider* NewLC();
void Destruct();
//implement the protocol
void HandleEvent(TInt aEventCode); //handle protocol
protected:
void ConstructL();
};
协议提供者类提供了HandleEvent()的明确处理。使用者可以如下调用:
LOCAL_C void doExampleL()
{
//show use of interface with simple class
CProtocolProvider* provider = CProtocolProvider::NewLC();
CProtocolUser* user = CProtocolUser::NewLC();
user->DoSomething(provider);
CleanupStack::PopAndDestroy(); //user
CleanupStack::PopAndDestroy(); //provider
}
这里的DoSomething函数需要一个MProtocol*参数,C++将这个CProtocolProvider*提供者指针强制转换为一个MProtocol*指针,因为MProtocol是CProtocolProvider基类之一。当DoSomething()处理HandleEvent()时,C++虚函数机制可以确保CProtocolProvider的HandleEvent()被调用。因此用户可以直接使用协议,而不需要知道任何关于具体协议提供者类的情况。
This method achieves the intended goals:
1、协议的使用者只与协议有关,而和任何特定的具体提供者无关。
2、the protocol can be introduced into a class hierarchy at any desired point, by multiple inheriting form a base class and one or more interfaces classes
3、可以形成不同协议的完全封装
4、there is no inconvenient intermediate class, with its programming difficulties and wasteful memory use
Because protocols may be mixed into the derivation hierarchy of conventional classes at any convenient point in the hierarchy, such protocol specification classes are sometimes also called mixins, the origin of the prefix M.
Arithmetic types
------------------
An int is usually implemented as the natural machine word size of the particular implementation. 在大多数的机器里是32位字节,在老的机器里可能是16位,在一些新的机器里甚至达到64位字节。
同样,一个指针(如void*)也通常被当作一个机器word来对待,不过有的机器里它可能更复杂。
It is assumed that the symbian platform is implemented on a machine with a 32-bit or greater machine word, and 32-bit pointers.The types TInt and TUint are typedefed onto the built-in int and unsigned int types, and are guaranteed to be at least 32 bits.
When you need a specific size, regardless of implementation, use a sized type.Serveral of these are available:
——TInt32 / TUint32
32位signed and unsigned integer
In each case, the representation is a 32-bit machine word which, in the ARM architecture, must be aligned to a four-byte boundary. The compiler ensures that this is always the case.
——TInt8 / TUint8 / TText8
8-bit signed and unsigned integer, and 8-bit character
In each case, the representation is an 8-bit byte, which has no specific alignment requirement.
——TInt16 / TUint16 / TText16
16-bit signed and unsigned integer, and 16-bit character
In each case, the representation is a 16-bit halfword, which should be aligned to a
two-byte boundary. The ARM architecture (prior to ARM8 and the Thumb instruction set) is relatively poor at handling 16-bit quantities, and their use is recommended only when space is at a premium, or for Unicode text handling.
——TInt64
64-bit unsigned ineger
Since ARM has no built-in support for 64-bit arithmetic. TInt64 is implemented as a C++ class.
——TReal / TReal64
双倍精度的浮点数,IEEE754 64-bit representation
这个是推荐使用的一般浮点数类型,ARM架构并没有提供浮点的支持,你应该尽量使用整型运算(例如,大多数GUI计算)只有点程序真的需要时(如电子表格程序)才不得不使用这些浮点类型。
——TReal32
32-bit floating point
This is smaller and quicker, but should only be used when space and /or time are at a true premium, as its precision is unsatisfactory for many application.
Compound types
----------------
Apart from classes, C++ inherits from C various other types of compounding
A struct maps an area of memory
struct TEg
{
TInt iTnt; //offset 0, 4 bytes
TText8 iText; //offset 4, 1 byte
//3 wasted bytes
TReal iReal; //offset 8, 8 bytes
} //total length = 16 bytes
//都是以4 bytes = 32 bits为一个单位的
结构被看做是T types:那是因为他们都没有自己的堆分配资源(如C type classess样)
一个数组包括很多built-ins or other types
TInt a[32]; //32 TInts, = 128 bytes
S b[3]; // 3 S's, = 48 bytes
使用C++数组的最大缺点就是没有对其索引的自动检查。基于这个理由,也为了支持更多复杂的container,C++已经开始发展STL。symbian并不支持STL,但有它自己的一套有效的解决方案。
Pointers
----------
指针存放了内存地址。
S* ps; // pointer to an S
ps = &s; //take address of existing S
通常我们需要一个指向包含任何可能内容的内存的指针,在C++中我们通常用void*指针来表示,但在symbian平台中,我们用TAny* 来代替它。
Strings
----------
在C++中,基本的字符串就是一个字符数组:
char* hello = "hello";
这段做两件事情:首先,它在内存中为'h','e','l','l','o',''分配六个字节,其次,它设定一个指针hello来指向这一内存的首地址。
在c++库中,处理字符串要顾及到其越界后可能需要的扩展,在symbian中的解决方法是使用descriptors
Functions
--------------
函数是在程序中可以任意调用执行的一段代码,而堆栈常常用来传递参数和存放局部变量,堆栈可以由机器的register而进行扩展,象ARM这种寄存器异常丰富的系统,内存都不经常被使用。But, conceptually, there is a stack, and for the purposes of this explanation it is convenient to consider the stack as if it were implemented entirely in memory.
参数通过拷贝或求值被传递到调用函数的堆栈段中,在参数中传递过多的数据是不明智的,事实上,就是任何超过2个机器字的数据量都不被提倡。因为这将引起过多的拷贝动作,相反,用一个指针或是引用来处理这些数据的地址要比传递数据本身好得多:)
在多任务的操作系统中(如symbian这样的平台),每个线程都有它自己的堆栈,这是一个预先分配的内存。使用堆栈的好处就是分配和解除分配空间是非常迅速的——只有一对指令(Push/Pop)。同样,在堆栈中的变量的生命期是很明确的:与它所属函数的生命期是一致的。
当函数返回时,它的堆栈空间仍然在那,只是没有被重新分配。当下个函数被调用时它将仍被使用。一个潜在的严重错误就是,我们在函数中创建了新的对象,最后返回一个指向它的指针:
TEg* foo()
{
TEg s;
TEg* ps = &s;
return ps; // !!error!!
}
事实上,这个指针是无效的,因为该内存空间已经不属于foo函数。里面的数据也就不确定了。
This error is so obvious that a compiler will trap it. But it can occur in more subtle forms:
foo(CContainer* aContainer)
{
TEg s;
TEg* ps=&s;
aContainer->iMember=ps;
} //真毒:)真狡猾
These cannot be trapped so easily.
Heap
------
每个线程都有个堆,你能在需要的时候在上面创建新的对象或释放对象。使用堆的好处就是,对象的生命周期完全掌握在你的手里,这也带来了职责,你在用完该对象时可别忘记了释放它,并且不能再使用一个指针指向已经被释放的对象了。
[Object orientation basics]
Relationships
---------------
在面向对象系统中,在对象之间的关系很多。以下是一些列子:
1、the control environment(CCoeEnv)有一个window server session(RWsSession)。window server session的生命周期是和control environment一样的。
2、the app UI(CEikAppUi)使用一个文档(CEikDocument),The document has independent existence: the app UI may come into being for a while, and then be destroyed, but throughout the app UI's lifetime, it uses the document.
[Object lifetimes and cleanup]
overview
----------
在堆和栈中,对象都有下列近似的生命周期:
1、在堆或栈上为对象分配内存
2、初始化:即把内存的内容设定为有用的值
3、使用该对象
4、清除:清除任何该对象所使用的资源
5、在堆或栈上释放响应内存空间
对象的生命周期是一个基本的概念:在某些操作系统中,它会被忽视,因为在那里当程序中断后堆和栈会被释放。在symbain平台上,程序是为了数月运行而不中断的。因此当他们的生命周期结束后就立即被清除是很重要的,无论他们是在堆或栈中分配的,或者是因为正常运行或错误运行而终止的。
Lifetimes in C
----------------
在C堆栈中,可能看起来如下:
#include "s.h"
void foo()
{
S s;
sInitialize(&s, p1, p2);
sUse(&s, p3, p4);
sCleanup(&s);
}
S对象的分配是在函数入口处进行的,并且在退出时释放空间。
在C堆中,应该看起来如下:
void foo(0
{
S* s = (S*)malloc(sizeof(S));
//should really check this succeded!!
sInitialize(s, p1, p2);
sUse(s, p3, p4);
sCleanup(s);
free(s);
}
这里得到的s是指向对象的一个地址了,做为参数就更加简便了:)
Mostly, the lifetime of a heap-based object would not be contained within a single function like this: it might be created from one function, used from another, and destroyed from another.
Lifetimes in C++
-------------------
C++中对象的生命周期从构造函数开始,结束于析构函数。它还拥有一个new()操作,比malloc()要更好。以及相应的delete,这也是比free()好的。
在C++的堆栈中,一个对象生命周期看起来如下:
void foo()
{
S s(p1, p2); //invokes constructor
s.Use(p3, p4); //nice syntax;
} //invokes destructor
注意,如果说出现意外情况,例如use()函数调用时失败,那函数就不能正常返回。这样析构函数就不能被正确调用。我们来看一下symbian平台是如何处理这些的:
在C++堆中,对象生命周期看起来如下:
void foo()
{
S* s = new S(p1, p2); //allocate, construct - should really check
s->Use(p3, p4);
delete s; // destruct, de-allocate
}
Again, the syntax is much nicer. Only one thing cannot be provided by C++: the user of a class must still remember to delete the object at the end of its lifetime.
Lifetimes in the Symbian platform
-----------------------------------
symbian平台中堆栈上的对象的生命周期和标准C++中的非常相似,不过对其的控制有不同,如下:
void Fool()
{
CS* s = new (ELeave) CS; //allocate and check
CleanupStack::PushL(s); //push, just in case
s->ConstructL(p1, p2); //finish constructing - might leave
s->UseL(p3, p4); //use - might leave
CleanupStack::PopAndDestroy(); //destruct, de-allocate
}
上面的代码段展示了四件大事
1、all heap-based classes have names beginning with C: they are in fact derived from a single base class, CBase, which exists solely to suport easy cleanup
2、a cleanup stack is used to hold references to objects: if a leave occurs due to out-of-memory or some other error, objects held on the cleanup stack are popped from it, and destroyed. In the case of CBase* objects pushed to the stack, they are destroyed by calling their C++ destructor. The CBase class has a virtual destructor (CBase::~CBase()) which makes this possible.
3、any function which might leave is designated by a trailing L in its name. When you see a function that might leave, you must always ask what would happen if it did leave, and what would happen if it did not. The operating system provides all the program infrastructure required to allow objects to be de-allocated even when a leave occurs, but without burdening the programmer.
4、new (ELeave) is an overloaded operator new() function, which will leave if it fails to allocate the required memory. It never returns a null pointer.
另外还有两件事情值得注意:
1、since the cleanup stack itself requires memory allocation for each stack frame, a push might leave. The PushL() function reflects this in its name. The cleanup stack is guaranteed to have a free slot before a CleanupStack::PushL(), so that the object reference will always be successfully stored on the stack. If a leave occurs when allocating the next stack frame, the object will be popped and destroyed as normal.
2、the C++ constructor must not leave. For objects whose construction requires resource allocation or any other operation that might fail, this means that construction must be separated into a C++ constructor that does not leave, and another initialisation function that might leave, which is conventionally called ConstructL().清除栈、CBase以及双重构造都是操作系统的核心。
[Multiple inheritance and interfaces]
Overview
----------
多重继承是C++的一个重要特性。
在symbian平台中多重继承只有一个简单的目的:也就是,定义接口协议。(这个我们在com定义接口的方法中也能看到,一是用多重继承二是用嵌套类,很值得研究:)它们被用于下列情况:有一个协议提供者类,和一个协议使用者。最理想的状况是协议的使用者完全不关心协议提供者的(这个在com中也是的,使用接口而不关心接口是如何实现的),当然除了那些提供特殊协议的情况。这样的例子包括:
1、一个应用程序control就是一个协议提供者,它的菜单使用了菜单observing的协议。当一个菜单选项被选中以后,菜单observing 的协议就被执行,所以应用程序control就可以处理菜单命令了。除此以外,菜单control不知道关于应用程序control的任何东西。
2、一个应用程序,如电子表格,可能有一个引擎,which provides protocols for updating and getting its model contents, and a user interface, which uses these protocols to drive the engine. The engine is written with no knowledge of the user interface, and the user interface is written with minimal knowledge of the engine. 他们之间的交互依靠引擎所提供的协议:)
要理解为什么使用接口,下面可以一一说明:
1、the traditional method which uses single inheritance
2、a technique of overcoming the disadvantages of single inheritance, using protocol intermediary classes
3、a better technique, which uses multiple inheritance with interface classes
4、the restrictions on C++ multiple inheritance in Symbian OS
Protocols using classic single inheritance
--------------------------------------------
A classical use of single inheritance is to define an abstract protocol from which derived classes may inherit. A base class defines a protocol:
class CProtocol: public CBase
{
public:
virtual void HandleEvent(TInt aEventCode) = 0;
};
这个协议只包含一个函数HandleEvent(), 这里的事件由一个整型事件代码指明。
一个具体的协议提供类是从以上基类派生过来的,它提供了基类中纯虚函数的具体处理:
class CProtocolProvider: public CProtocol
{
public:
//construct/destruct
static CProtocolProvider* NewLC();
void Destruct();
//implement the protocol
void HandleEvent(TInt aEventCode); //handle protocol
protected:
void ConstructL();
};
另外,有个协议使用者类,它并不知道CProtocolProvider的一切,但它知道CProtocol类和指定协议的那个函数。它有个函数会使用HandleEvent():
void CProtocolUser::DoSomething(CProtocol* aProtocol)
{
_LIT(KOutput1, "External system doing something ");
_LIT(KOutput2, "invoking protocol - event 3 ");
testConsole.Printf(KOutput1);
testConsole.Printf(KOutput2);
aProtocol->HandleEvent(3); //handle an event
};
在CProtocol中定义的虚函数是由CProtocolProvider提供实现的,下面是代码:
void CProtocolProvider::HandleEvent(TInt aEventCode)
{
//handle an event in the protocol user
_LIT(KOutput1, "CProtocolProvider handling event %d ");
testConsole.Printf(KOutput1, aEventCode);
}
因此,尽管协议的使用者并不知道派生类CProtocolProvider,但可以通过一个指向这个派生类的指针来访问其成员函数
void doExampleL()
{
//show use of interface with simple class
CProtocolProvider* provider = CProtocolProvider::NewLC();
CProtocolUser* user = CProtocolUser::NewL();
user->DoSomething(provider);
CleanupStack::PopAndDestroy(); //user后进先出:)
CleanupStack::PopAndDestroy(); //provider
}
在上面的情况中,provider指针被强制转换成CProtocol* base,这是CProtocolUser::DoSomething()的需要.
使用这种方法的优势在于:
它促成了协议使用者于协议提供者的无关性。
这就是我们最想追求的状态,但这个方法也有很严重的缺点:
1、它强制协议的提供者要从一个协议基类派生
2、可以,如果有多个协议要被提供,那唯一的方法就是在一个single umbrella protocol中包含所有的需要协议,这是个很糟糕的封状。首先基类会变得很庞大,并且要从这么辨认这么多成员函数以及所属协议也不是件很容易的事情。其次,it may be desirable to have another provider class which provides some of the protocols provided by the first class, and others in addition. To support this requires an even larger umbrella protocol.因此,这种方法经常导致大的基类,协议间彼此无关。
Protocols using an intermediate class
----------------------------------------
如果要克服这样的缺点,我们建议使用一个间接的协议对象,它包含一个指向协议提供者的指针。基本的协议类是一样的,如下:
class TProtocol
{
public:
virtual void HandleEvent(TInt aEventCode) = 0;
};
but there is now a derived class for use with the CProtocolProvider only:
class TProtocolProviderIntermediary: public TProtocol
{
public:
//construct
TProtocolProviderIntermediary(CProtocolProvider* aRealProvider);
//protocol itself
void HandleEvent(TInt aEventCode);
private:
CProtocolProvider* iRealProvider; //real provider
};
This class provides the protocol as far as the protocol user is concerned. The concrete implementation of HandleEvent() just passes the function call to the real protocol provider class, which has a non-virtual DoHandleEvent() to provide the required functionality:
void TProtocolProviderIntermediary::HandleEvent(TInt aEventCode)
{
iRealProvider->DoHandleEvent(aEventCode);
}
With this system, CProtocolProvider is derived, not from the protocol definition class, but from CBase
class CProtocolProvider: public CBase
{
public:
//construct / destruct
static CProtocolProvider* NewLC();
void Destruct();
//implement the protocol
void DoHandleEvent(TInt aEventCode); //handle protocol
protected:
void ConstructL();
public:
TProtocolProviderIntermediary* iProviderIntermediary;
}
TProtocolProviderIntermediary是在CProtocolProvider的构造函数中被构造的,并且被它的析构函数所释放。基于这个原因,TProtocolProviderIntermediary是一个T类:它并不拥有CProtocolProvider,and cannot be orphaned.
When a function in the protocol user requiring the protocol provider is called, it must now be called passing the intermediary object as a parameter:
LOCAL_C void doExampleL()
{
//show use of interface with simple class
CProtocolProvier* provider = CProtocolProvider::NewLC();
CProtocolUser* user = CProtocolUser::NewLC();
user->DoSomething(provider->iProviderIntermediary);
CleanupStack::PopAndDestroy(); //user
CleanupStack::PopAndDestroy(0; //provider
}
协议使用者的DoSomething()本质上是和以前一样的,除了其参数现在变成一个TProtocol*。因此,用户只要知道基类TProtocol就可以了,虚函数机制导致间接派生的HandleEvent()被调用了,这个函数实际上调用的是真正协议提供者的DoHandleEvent()。
这种方法解决了困扰single inheritance的问题:
1、现在,任何数目的协议都可以被支持了,并且可以被分别封装,每个协议都需要一个intermediary class,并且每个间接类的对象都要指向相应的实际协议提供者类的对象。
2、no large base classes are needed to provide umbrellas for servral protocols.
但是,它还是有很严重的缺点:
1、这是很笨拙的:not only does each protocol require an abstract class (which cannot be avoided), but also, at each point in the derivation tree at which a protocol is introduced, a derived protocol class must be written which implements the protocol for the relevant class which really provides the protocol: further, the derived protocol object and the real protocol provider must be linked
2、if there are many classes which use many protocols in this way, not only is the method cumbersome to program, but it is uneconomical on memory, since each derived protocol class object requires at least two machine words of heap memory. This consideration becomes more serious if there are more small real protocol providers, providing many different protocols.
Protocols using interface classes
------------------------------------
这些问题的解决可以通过多重继承,一个基类MProtocol申明了那个协议:
class MProtocol
{
public:
virtual void HandleEvent(TInt aEventCode) = 0;
};
这次协议的提供者是从CBase和MProtocol派生的:)
class CProtocolProvider: public CBase, public MProtocol
{
public:
//construct/ destruct
static CProtocolProvider* NewLC();
void Destruct();
//implement the protocol
void HandleEvent(TInt aEventCode); //handle protocol
protected:
void ConstructL();
};
协议提供者类提供了HandleEvent()的明确处理。使用者可以如下调用:
LOCAL_C void doExampleL()
{
//show use of interface with simple class
CProtocolProvider* provider = CProtocolProvider::NewLC();
CProtocolUser* user = CProtocolUser::NewLC();
user->DoSomething(provider);
CleanupStack::PopAndDestroy(); //user
CleanupStack::PopAndDestroy(); //provider
}
这里的DoSomething函数需要一个MProtocol*参数,C++将这个CProtocolProvider*提供者指针强制转换为一个MProtocol*指针,因为MProtocol是CProtocolProvider基类之一。当DoSomething()处理HandleEvent()时,C++虚函数机制可以确保CProtocolProvider的HandleEvent()被调用。因此用户可以直接使用协议,而不需要知道任何关于具体协议提供者类的情况。
This method achieves the intended goals:
1、协议的使用者只与协议有关,而和任何特定的具体提供者无关。
2、the protocol can be introduced into a class hierarchy at any desired point, by multiple inheriting form a base class and one or more interfaces classes
3、可以形成不同协议的完全封装
4、there is no inconvenient intermediate class, with its programming difficulties and wasteful memory use
Because protocols may be mixed into the derivation hierarchy of conventional classes at any convenient point in the hierarchy, such protocol specification classes are sometimes also called mixins, the origin of the prefix M.
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/10294527/viewspace-126255/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/10294527/viewspace-126255/