【二】 Symbian对象构造

C++的纯手工内存管理,确实是一个万恶之源。在对象构造时,有一个著名的内存泄漏隐患问题。比如一个类如下:

class A
{
public:
        A()
        {
                a1 = new T1();
                a2 = new T2();
                ...
                an = new Tn();
        }

private:
        T1 * a1;
        T2 * a2;
        ...
        Tn * an;
}

当你调用 new A() 进行分配的时候,一旦失败,可能导致内存的泄露。比如系统正吭哧吭哧分配到了a18,失败了,抛出异常了,或者返回空值了,前面a1 - a17个对象,就彻底成了没娘管的娃,一并泄漏了出去。一个解决策略是,管好每一个分配过的对象,一旦有问题,清空一切。比如 a18分配失败了,delete掉a1 - a17。且不说这么做有没有其他问题,但是这份苦力,估计就没多少人能够承受。

二阶段构造

为了解决对象分配的问题,Symbian琢磨了所谓的二阶段构造法,它是一个pattern,关键在于将对象中栈数据的初始化和堆对象的分配过程隔离开来。一个标准的二阶段构造类如下:

class A
{
public:
        ~A();
        static A * NewL();
        static A * NewLC();

private:
        A();
        void ConstructL();
}

其中内容,自动构造的每个Symbian C++类中都会有。在构造函数中,只能够执行赋值等操作,就是初始化栈中内容,整个操作不会涉及到堆中对象的分配。所有需要分配的堆中对象,推迟到ConstructL函数中进行。NewL和NewLC提供一个封装,将构造函数和二阶段构造函数封装一起。当然,仅通过这样的方式,无法解决内存泄漏的问题,一个核心机制,是清理栈,即CleanupStack。

清理栈

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,少了哪一样,整个机制全部白搭。

Symbian的异常处理

Symbian的异常处理,就是著名的Leave机制,如果你打开TRAP宏,便惊奇的发现,所谓Leave,只不过老瓶装新酒,它只是给C++的异常机制,穿了个丁字裤,还是超节约布料型的。你可以将所谓的TRAD看成是catch,将Leave看成throw,将带L的函数,看成是throw exception的函数,再将err code当作是异常类型,整个Leave机制,就和C++的异常匹配上了。
当然,之所以称为老瓶装新酒,那么就有一些可以称为新的琐碎事。首先,就是对CleanupStack的维系。在TRAP宏和User::Leave中,包含了对CleanupStack的标记的管理和资源清理,没有它们,CleanupStack这套东东,就该另辟蹊径了。
而另一方面,就是对标准异常和无法估量的异常进行了分门别类的处理。C++和.Net不一样,异常都是不同根的,我们往往需要用catch(...)去处理一些杂类的状况。在TRAD中,对所以Symbian中的异常进行了分类。一类是派生自XLeaveException的异常,它们是整个Symbian的Cleanup以及Leave的管辖范围,只有在触发此类异常的时候,所谓的自动释放内存、Leave才能发挥作用;而其他所有的异常,都被归类异类,一旦发生,直接User::Invariant()来安乐死。所以,你明明是TRAP了,在读到空指针等错误发生的时候,它完全不起作用,程序直接崩溃,因为,这超出了它的能力范畴。
除此之外,Symbian开始支持标准的C++异常了,但对于一个合格的Symbian开发者而言,了解这些,还是有益无害的。。。

转载于:https://blog.51cto.com/duguguiyu/362831

Symbian手记【二】 —— Symbian对象构造相关推荐

  1. C++内存分配与对象构造的分离

    在C++中,我们基本用new(delete)操作符分配(释放)内存.new操作符为特定类型分配内存,并在新分配的内存中构造该类型的一个对象.new表达式自动运行合适的构造函数来初始化每个动态分配的类类 ...

  2. C++类的使用(二)—— explicit构造与const成员赋值

    一.代码实例 class Class {public:Class(int x){_x = x;}int getX(){return _x;}private:int _x; };Class object ...

  3. Java基础学习之(二)—对象与类的方法参数

    一.Java中,方法参数的使用情况: 1.一个方法不能修改一个基本数据类型的参数: 2.一个方法可以改变一个对象参数的状态: 3.一个方法不能让对象参数引用一个新的对象: 例子代码为: package ...

  4. C# CAD对象 构造时应把它的父对象也加进它的属性里

    C#  CAD对象  构造时应把它的父对象也加进它的属性里 因为你要根据这一级找它的上一级 转载于:https://www.cnblogs.com/houlinbo/archive/2009/08/2 ...

  5. Delphi - 对象构造浅析后续

    技术交流,DH讲解. 之前一篇文章已经讲过对象构造的过程,但是我们那个对象无任何东西,这里我们在已有的基础上面加点儿东西再来看看. 代码改成: THuangJacky = classprivateFN ...

  6. C++继承体系下的对象构造

    继承体系下的对象构造 继承下的对象构造 虚拟继承 初始化"虚基类子对象" vptr的设置 总结 继承下的对象构造 class Point{public:Point(float x ...

  7. c++无继承情况下的对象构造

    无继承情况下的对象构造 C struct的Point声明 在C和C++中有什么区别? 抽象数据类型 包含虚函数的Point声明 自定义构造函数中会安插初始化vptr的代码 以成员为基础的赋值操作 C ...

  8. c++多个对象构造和析构

    多个对象构造和析构 对象初始化列表 对象初始化列表出现原因 注意概念 注意 总结 对象初始化列表 对象初始化列表出现原因 1.必须这样做: 如果我们有一个类成员,它本身是一个类或者是一个结构,而且这个 ...

  9. $emit传递多个参数_10年架构师深解java核心技术:方法参数+对象构造,确定不学?...

    方法参数 首先回顾一下在程序设计语言中有关参数传递给方法(或函数)的一些专业术语.值调用(call by value)表示方法接收的是调用者提供的值.而引用调用(call by reference)表 ...

最新文章

  1. C语言基础知识【常量】
  2. FTP服务器端程序分类
  3. leetcode18
  4. 媒体洞察 | 让企业自由发展的云时代
  5. C语言经典例79-字符串排序
  6. 【计算理论】图灵机 ( 非确定性图灵机 | 非确定性图灵机指令分析 | 计算过程 | 非确定性指令出现多个分支 | 非确定性图灵机转为计算树 | 计算树 )
  7. 集成学习-Boosting集成学习算法XGBoost
  8. php form表单属性,HTML5 表单属性
  9. 高通发布一系列新型WiFi芯片:兼容WiFi 6技术
  10. Python对json数据的操作(香烟示例)
  11. springboot显示信息并且修改_Spring Boot小结-03--增.删.改.查
  12. java记事本教程_使用记事本开发java程序的步骤
  13. 微积分手机版 pk 清华大学微积分教程
  14. 谷歌无法加载印象笔记剪辑插件
  15. 二级计算机vf题型,计算机二级VF考试常见题型与解题技巧
  16. html 输出helloworld,以及基本结构详解
  17. 计算机网线怎么连接另一台电脑,教你如何用一根网线将两台电脑直连
  18. C/C++学习笔记(2020.11---2021.5)
  19. 英语: 听力(Listening)
  20. 正阅读微信小说分销系统-视频教程-1.渠道商-公众号配置-基础信息

热门文章

  1. join丢失数据_15、Hive数据倾斜与解决方案
  2. linux 默认组,系统自动创建的默认安全组和自己创建的安全组的默认规则
  3. mysql getname_mysql别名取不出值(getColumnLabel和getColumnName的区别)
  4. joomla 3.6 mysql 版本_Joomla是否支持MariaDB数据库?
  5. mysql重复你数据标识_MySQL 处理重复数据
  6. 基于增量更新的协同过滤
  7. centeros php,CenterOs7 安装oracle19c的方法详解
  8. linux服务器 授权命令,linux的Sudo/su授权命令详解
  9. 软件测试颗粒度,测试用例粒度粗细的划分
  10. php助理工作内容,生产助理的工作职责