泓安园小区物业:Symbian手记【二】 —— Symbian对象构造
来源:百度文库 编辑:偶看新闻 时间:2024/05/02 09:58:59
C++的纯手工内存管理,确实是一个万恶之源。在对象构造时,有一个著名的内存泄漏隐患问题。比如一个类如下:
class A当你调用 new A() 进行分配的时候,一旦失败,可能导致内存的泄露。比如系统正吭哧吭哧分配到了a18,失败了,抛出异常了,或者返回空值了,前面a1 - a17个对象,就彻底成了没娘管的娃,一并泄漏了出去。一个解决策略是,管好每一个分配过的对象,一旦有问题,清空一切。比如 a18分配失败了,delete掉a1 - a17。且不说这么做有没有其他问题,但是这份苦力,估计就没多少人能够承受。
{
public: A()
{ a1 = new T1();
a2 = new T2();
...
an = new Tn();
}
private: T1 * a1;
T2 * a2;
...
Tn * an;
}
二阶段构造
为了解决对象分配的问题,Symbian琢磨了所谓的二阶段构造法,它是一个pattern,关键在于将对象中栈数据的初始化和堆对象的分配过程隔离开来。一个标准的二阶段构造类如下:class A其中内容,自动构造的每个Symbian C++类中都会有。在构造函数中,只能够执行赋值等操作,就是初始化栈中内容,整个操作不会涉及到堆中对象的分配。所有需要分配的堆中对象,推迟到ConstructL函数中进行。NewL和NewLC提供一个封装,将构造函数和二阶段构造函数封装一起。当然,仅通过这样的方式,无法解决内存泄漏的问题,一个核心机制,是清理栈,即CleanupStack。
{
public:
~A();
static A * NewL();
static A * NewLC();
private:
A();
void ConstructL();
}
清理栈
CleanupStack是单件的形式呈现在程序中,GUI的程序系统为你构造好了,Console的需要人肉一个。当你在一个函数中,new了一个对象,你需要先把它push到CleanupStack中,才能调用其带L的方法,并在调用完成后将它pop出CleanupStack。一旦L函数执行失败,Leave了,并在上层用TRAP宏抓到这个Leave错误,系统会自动释放存放在CleanupStack中,还没来得及pop的对象,以保证所有资源都不会泄漏。要做到这点,有两个需要解决的问题,一是如何不在人肉delete的情形下自动析构,第二个是如何知道析构栈中多少个对象。解决第一个问题,关键就是利用栈对象的析构函数,每个push到CleanupStack中的对象,都被一个栈对象TCleanupItem封装了一下,作为一个成员变量TAny* iPtr存放起来。当这个栈对象被释放,会调用其析构函数,析构函数中包含delete iPtr的调用,如此,自动析构得以完成。当然,为了保持其通用性,TCleanupItem其实不是直接delete,而是通过一个TCleanupOperation的对象来实现的,这个对象负责在其析构函数中delete iPtr,当然,除了delete,不同的TCleanupOperation还可以是iPtr->close,iPtr->release之类的,这样可以将其机制轻松的扩展开来。#define TRAP(_r, _s) { \TInt& __rref = _r; \__rref = 0; \{ TRAP_INSTRUMENTATION_START; } \try { \__WIN32SEHTRAP \TTrapHandler* ____t = User::MarkCleanupStack(); \_s; \User::UnMarkCleanupStack(____t); \{ TRAP_INSTRUMENTATION_NOLEAVE; } \__WIN32SEHUNTRAP \} \catch (XLeaveException& l) \{ \__rref = l.GetReason(); \{ TRAP_INSTRUMENTATION_LEAVE(__rref); } \} \catch (...) \{ \User::Invariant(); \} \{ TRAP_INSTRUMENTATION_END; } \}另一个问题解决之道,就是记录一个level,在函数执行前放入一个标记,一旦有错误,就消除在此标记后push进来的对象。这个机制的维系,隐藏在TRAP宏中。当你写TRAP(err, DoitL())时,TRAP会在调用DoitL()前,调用User::MarkCleanupStack()加入一个标记,并在调用结束后利用User::UnMarkCleanupStack检查并且消除该标记。放一个标记在这里,一旦你多push了少pop了,或者少push了多Pop了,都会触发异常,谨防顺手写错。而在执行函数DoitL()过程中,一旦发生Leave错误,在User::Leave()之类的函数中,都会找到最后标记的位置,清除标记后push的所有对象。由于栈和函数调用都属于先进先出的,整个机制是可以嵌套进行的。只要你TRAP了Leave错误,所有资源都会被保证析构(如果没有TRAP,天皇老子都帮不了你...)。这种半自动半人肉的内存管理方式,虽然不能帮助复杂的内存对象生命周期的维护,但至少可以保证每一个资源在异常时正常释放,这一点在嵌入式系统中比一般系统显得更为重要(因为内存紧张,分配不成功是常态...)。但人肉方式总归是要人来解决的,不论CleanupStack多么的好,它只是一个pattern,它不能自动去做一些事情,还是需要开发人员主动的push,pop,leave,以及TRAD,少了哪一样,整个机制全部白搭。