Inside CBase class - six essential questions

来源:互联网 发布:二次元图片下载软件 编辑:程序博客网 时间:2024/06/05 04:31

Everybody knowsC-class in Symbian, the so called C-class is the one derived from classCBase. CBase is widely used in Symbian APIs, because it represents theclass which should be created on heap. Every Symbian programmer knowshow to call NewL() or NewLC() ( may be new (ELeave) ) of the CBasederived class to create the object, but not many people would reallylook into the CBase class itself to see why it has some interestingfeatures.

If you can answer thefollowing questions, you can skip this article, because you are aSymbian programmer with strong curiosity. If you are not sure aboutsome answers, I recommend you to read this ariticle, because CBaseclass is essential in Symbian OS and it's interesting to know somefeatures of this class. The questions are:

1. Why does cleanup stack has 3 versions of PushL() including PushL( CBase *aPtr )?
2. Why does CBase have a public virtual destructor?
3. How is CBase derived object initialized to binary zeroes?
4. Why is CBase derived object initialized to binary zeroes?
5. Why use new[] to initialize CBase derived object is not recommended?
6. Why does CBase has a private copy constructor and a private operator = function?

Let's get into these questions one by one.

Why does cleanup stack has 3 versions of PushL() including PushL(CBase *aPtr)?

It'san interesting question, there're 3 versions of PushL() inCleanupStack, they're PushL(TAny *aPtr), PushL(CBase *aPtr) andPushL(TCleanupItem anItem), why not just PushL(TAny *aPtr) andPushL(TCleanupItem anItem)? Let's see how cleanup stack works. Usuallywe use the code like this:

  1. CTest* test = CTest::NewL(); // CTest is a CBase derived class
  2. CleanupStack::PushL( test );
  3. test->FunL();
  4. CleanupStack::PopAndDestroy();

It'sthe regular use of cleanup stack, push the pointer "test" into thecleanup stack because FunL() may leave, after that, if everything isfine, pop the pointer and destory the object. Let's consider how doescleanup stack destory the object when calling PopAndDestroy(),according to the SDK helper, "If the item on the stack is a CBase*pointer, the pointer is removed from the stack and the object isdestroyed with delete. If the item on the stack is a TAny* pointer, thepointer is removed from the stack and the memory occupied by the objectis freed with User::Free()."

Whydoes cleanup stack has to judge if the pointer's type is CBase* orTAny*? Becasue a class may provide a private destructor! If a class hasa private destructor, calling delete on this pointer will be invalid.In this case, system only calls User::Free() to free the memory of theobject itself but can't invoke its destructor.

Whathappens to CBase derived class? If you take a look at e32base.h(thedeclaration of CBase is inside, actually part of the declaration), youwill find CBase has a public virtual destructor. This ensures thecleanup stack can call delete on the CBase and its derived classes'pointers. It's useful to keep this in mind that if you push a non-CBaseclass pointer into the cleanup stack, the stack won't call your class'sdestructor. So, in most of the cases, you would like to either pushCBase derived class into cleanup stack or never allocate heap memory inother types of classes.

But ifyou really want to allocate heap memory in other types of classes, thethird version of PushL() can help you out. What you need to do isdefine a function which will do the cleanup and wrap the object byTCleanupItem.

Why does CBase have a public virtual destructor?

Wecan divide this question into 2 parts, why virtual, why public? Theanswer above tells you why public. The reason to make it virtual issimple. Sometimes you want to write the code like this:

  1. CBase* test = CTest::NewL(); // CTest is a CBase derived class
  2. CleanupStack::PushL( test );
  3. test->FunL();
  4. CleanupStack::PopAndDestroy();

With the virtual keyword, cleanup stack can make sure it will destroy the object properly by the base class's pointer.

How is CBase derived object initialized to binary zeroes?

Luckily,since all the new operator functions of CBase is inline, we can see theimplementation of every function in e32base.inl. For example for "TAny*operator new(TUint aSize, TLeave)" the implementation is :

inline TAny* CBase::operator new(TUint aSize, TLeave)
 { return User::AllocZL(aSize); }

Hereit uses User::AllocZL(), it allocates a cell of specified size from thecurrent thread's default heap, clears it to binary zeroes, and leavesif there is insufficient memory in the heap. That's how CBase derivedobject is initialized to binary zeroes?

Why is CBase derived object initialized to binary zeroes?

Let's consider the code below :

  1. CTest* CTest::NewLC()
  2. {
  3.  CTest* self = new ( ELeave ) CTest;
  4.  CleanupStack::PushL( self );
  5.  self->ConstructL()
  6.  return self;
  7. }
  8. void CTest::ConstructL()
  9. {
  10.  iPointer = CMustLeave::NewL(); // assume this leaves
  11. }
  12. CTest::~CTest()
  13. {
  14.  if( iPointer )
  15.  {
  16.   delete iPointer;
  17.   iPointer = NULL;
  18.  }
  19. }

IfCBase doesn't initialize the object to binary zero, and you don'tinitialize the iPointer to NULL manually, the initial value of iPointeris uncertain. Once CMustLeave::NewL() leaves, the value of iPointer isstill uncertain(in most of the cases it's not zero). Since in NewLC,CTest was pushed into the cleanup stack, so system will pop the pointerand call CTest's destructor. This will cause the problem, because theif condition will be true and you will call delete on a pointer whichdoesn't pointer to a legal memory. Mostly program will crash. You willnot meet this problem if iPointer was initialized to zero(NULL).

Why use new[] to initialize CBase derived object is not recommended?

There'rea number of overloaded new operator functions in CBase class, butthere's no new[] operator function. So if you use new[] to create CBaseobjects, you will not get the memory with binary zero. If you want tocreate a array of CBase derived class you can use the class likeRPointerArray to deal with it.

Why does CBase has a private copy constructor and a private operator = function?

This is a general method to prevent the developer from the shallow copy accidently. If you write the code like this :

  1. CBase* pointer = new ( ELeave ) CBase;
  2. CBase base = *pointer;  // call copy constructor

Thecompiler will complain "illegal access from CBase to protected/privatemember CBase::CBase(const CBase&)", because the second line willtry to call the copy constructor of CBase. If you write the code like :

  1. CBase* pointer = new ( ELeave ) CBase;
  2. CBase base;
  3. base = *pointer;  // call operator =

Thecompiler will also complain because it will call the operator =function. If you really want to do the deep copy you can write your ownpublic copy constructor and operator = function. The reason that CBasedo this is in most cases you will allocate some heap memory inside aCBase derived class, and it doesn't make sense(or I can say it'sdangerous)to use the default copy constructor or default operator =function of this kind of class. So CBase turns this feature off bydefault.

Actually, in Symbian, toprovide your own public version of copy contructor or operator =function is not a good idea neither. Because these 2 function are notleaving functions, but the code inside these 2 functions may leavesometimes( will call new (ELeave) or NewL() ). That's a paradox. Thegood manner is to provide a leaving function named, let's say, CloneL()to do the copy task.

This article is also published in www.NewLC.com
http://www.newlc.com/inside-cbase-class-six-essential-questions

原创粉丝点击