理解 Symbian C++ 的 NewL ConstructL NewLC ELeave

来源:互联网 发布:淘宝网店转让麻烦吗 编辑:程序博客网 时间:2024/06/05 19:18

理解 NewL ConstructL NewLC ELeave

 

初学Symbian开发,第一件感觉迷惑的事情是CleanupStack 第二件肯定是随处可见的NewL,NewLC,ConstructL。
这些函数的出现依然和内存泄漏有关,这是一种被称为两步构造的机制,英文叫Two-phase Construction。
我知道C++里面的 new 操作符实际上完成2件事,第一根据对象类的大小在堆上分配一块内存并获得指向内存的
指针,第二利用指针调用类的构造函数,最后把指针返回。

 

在Symbian上这样做是有隐患的,就是当你分配好了内存,但是调用构造函数的时候程序意外退出了,这样会造成
刚才分配的内存产生泄漏。只有那些放入CleanupStack的内存,在程序意外结束后会被释放,new 分配的内存在
调用构造函数之前还没有被放入CleanupStack呢。

 

Symbian的设计师为了解决这个问题,给所有开发者设计了一个编写程序的定式,这就是Two-phase Construction

具体是这样的:

把普通的new 操作分为2个步骤来进行,第一步只分配内存,当分配的内存被放入cleanupstack后,第二步进行构造。
但是在C++里 如何阻止编译器的new操作不调用构造函数呢?这个貌似不可以。。。


于是Symbian的设计者作了个规定,类在构造函数里不要做任何可能产生异常的操作,只能做那些绝对安全的事情,比如
简单的变量赋值,然后提供一个名字叫 ConstructL的函数,在这个函数里做所有类的初始化工作,当然包括那些危险
的可能导致异常的操作。

 

那么 Symbian的 类构造就变成了这样
比如 有一个叫 CFoo 的类,我想声明一个指针 p,创建一个CFoo的对象赋给 p
CFoo *p = new(ELeave) CFoo();
CleanupStack::PushL(p);
p->ConstructL();
CleanupStack::Pop();

这样写是不是有点太罗嗦?每个对象都要用4条语句创建,如果是频繁使用实在是太麻烦了。
于是Symbian的设计者又作了一个规定,每个类要实现一个NewL的static函数来完成上面的4条语句的工作


class CFoo
{
public:
 static CFoo *NewL()
 {
  CFoo *self = new(ELeave) CFoo();
  CleanupStack::PushL(self);
  self->ConstructL();
  CleanupStack::Pop();
  return self;
 }
}

有了NewL以后,调用CFoo的类的程序简化了


CFoo *p = CFoo::NewL();

那么NewLC又是什么呢?和NewL有什么不同?
有些类是这样的,他们提供一些方法,需要在对象创建完成后执行,但是这些方法也是会产生异常的比如
CFoo 如果有一个方法叫 DoSomethingL()

那么程序可以这样写吗?
CFoo *p = CFoo::NewL();
p->DoSomethingL();

显然这样写是有问题的正确的写法是
CFoo *p = CFoo::NewL();
CleanupStack::PushL(p);
p->DoSomethingL();
CleanupStack::Pop();

天啊,又是4条语句,太麻烦了。要知道在NewL结尾我们刚刚把CFoo的指针从CleanupStack里拿出来,马上就又放了进去。
是不是可以简化呢,那好我们再节约2条语句
NewL去掉结尾的CleanupStack::Pop();
 static CFoo *NewL()
 {
  CFoo *self = new(ELeave) CFoo();
  CleanupStack::PushL(self);
  self->ConstructL();
  return self;
 }
调用去掉CleanupStack::PushL
CFoo *p = CFoo::NewL();
p->DoSomethingL();
CleanupStack::Pop();

Symbian的设计者又规定了,具有以上行为的NewL 应该叫NewLC。表示指针返回后,没有从CleanupStack里取出,你可以继续调用一个危险的操作,在最后调用CleanupStack::Pop();

我发现 NewL 可以通过调用NewLC实现。
那么一个符合Symbian设计师的N条规定的类应该这样写

class CFoo
{
public:
 static CFoo *NewLC()
 {
  CFoo *self = new(ELeave) CFoo();
  CleanupStack::PushL(self);
  self->ConstructL();
  return self;
 }

 static CFoo *NewL()
 {
  CFoo *self = NewLC();
  CleanupStack::Pop();
  return self;
 }

 virtual ~CFoo()
 {
 }

protected:
 CFoo()
 {
 }

 void ConstructL()
 {
 // ....
 }
}

这里注意 CFoo的构造函数不能是Public的,为了防止使用者用new 或者在栈上创建对象。
析构函数要写成虚函数,这是纯C++问题,不明白的去看 More Effective C++

要说明一下,以上的写法是Symbian极力推荐的,但是不是硬性规定的,你只要保证没有内存泄漏
可以不这么写。
我个人还是推荐这样,这样的代码写Symbian程序的人都可以很好地理解。

最后说一下 new 之后为什么要有一个 (ELeave)。
new操作符是被Symbian重载过了,ELeave是给new的一个参数,他的意思是告诉new当无法分配内存时
程序就退出。比如内存不足的时候。

所以我们用了ELeave的话 就不用检查new 返回的指针了,能返回就一定是对的
如果出了错程序就结束掉了,new根本就不会返回。

NewL NewLC 是Symbian程序标志性的函数,所以有个Symbian开发的资源站点就叫 www.newlc.com

原创粉丝点击