您的位置:首页 > 产品设计 > UI/UE

Inside CBase class - six essential questions

2008-11-09 20:19 127 查看
Everybody knows
C-class in Symbian, the so called C-class is the one derived from class
CBase. CBase is widely used in Symbian APIs, because it represents the
class which should be created on heap. Every Symbian programmer knows
how to call NewL() or NewLC() ( may be new (ELeave) ) of the CBase
derived class to create the object, but not many people would really
look into the CBase class itself to see why it has some interesting
features.
If you can answer the
following questions, you can skip this article, because you are a
Symbian programmer with strong curiosity. If you are not sure about
some answers, I recommend you to read this ariticle, because CBase
class is essential in Symbian OS and it's interesting to know some
features 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's
an interesting question, there're 3 versions of PushL() in
CleanupStack, they're PushL(TAny *aPtr), PushL(CBase *aPtr) and
PushL(TCleanupItem anItem), why not just PushL(TAny *aPtr) and
PushL(TCleanupItem anItem)? Let's see how cleanup stack works. Usually
we use the code like this:
CTest* test = CTest::NewL(); // CTest is a CBase derived class
CleanupStack::PushL( test );
test->FunL();
CleanupStack::PopAndDestroy();

It's
the regular use of cleanup stack, push the pointer "test" into the
cleanup stack because FunL() may leave, after that, if everything is
fine, pop the pointer and destory the object. Let's consider how does
cleanup 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 is
destroyed with delete. If the item on the stack is a TAny* pointer, the
pointer is removed from the stack and the memory occupied by the object
is freed with User::Free()."
Why
does cleanup stack has to judge if the pointer's type is CBase* or
TAny*? Becasue a class may provide a private destructor! If a class has
a private destructor, calling delete on this pointer will be invalid.
In this case, system only calls User::Free() to free the memory of the
object itself but can't invoke its destructor.
What
happens to CBase derived class? If you take a look at e32base.h(the
declaration of CBase is inside, actually part of the declaration), you
will find CBase has a public virtual destructor. This ensures the
cleanup 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-CBase
class pointer into the cleanup stack, the stack won't call your class's
destructor. So, in most of the cases, you would like to either push
CBase derived class into cleanup stack or never allocate heap memory in
other types of classes.
But if
you really want to allocate heap memory in other types of classes, the
third version of PushL() can help you out. What you need to do is
define a function which will do the cleanup and wrap the object by
TCleanupItem.
Why does CBase have a public virtual destructor?

We
can divide this question into 2 parts, why virtual, why public? The
answer above tells you why public. The reason to make it virtual is
simple. Sometimes you want to write the code like this:
CBase* test = CTest::NewL(); // CTest is a CBase derived class
CleanupStack::PushL( test );
test->FunL();
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 the
implementation 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); }
Here
it uses User::AllocZL(), it allocates a cell of specified size from the
current thread's default heap, clears it to binary zeroes, and leaves
if there is insufficient memory in the heap. That's how CBase derived
object is initialized to binary zeroes?

Why is CBase derived object initialized to binary zeroes?

Let's consider the code below :
CTest* CTest::NewLC()
{
CTest* self = new ( ELeave ) CTest;
CleanupStack::PushL( self );
self->ConstructL()
return self;
}

void CTest::ConstructL()
{
iPointer = CMustLeave::NewL(); // assume this leaves
}

CTest::~CTest()
{
if( iPointer )
{
delete iPointer;
iPointer = NULL;
}
}

If
CBase doesn't initialize the object to binary zero, and you don't
initialize the iPointer to NULL manually, the initial value of iPointer
is uncertain. Once CMustLeave::NewL() leaves, the value of iPointer is
still 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 pointer
and call CTest's destructor. This will cause the problem, because the
if condition will be true and you will call delete on a pointer which
doesn't pointer to a legal memory. Mostly program will crash. You will
not meet this problem if iPointer was initialized to zero(NULL).
Why use new[] to initialize CBase derived object is not recommended?

There're
a number of overloaded new operator functions in CBase class, but
there's no new[] operator function. So if you use new[] to create CBase
objects, you will not get the memory with binary zero. If you want to
create a array of CBase derived class you can use the class like
RPointerArray 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 :
CBase* pointer = new ( ELeave ) CBase;
CBase base = *pointer; // call copy constructor

The
compiler will complain "illegal access from CBase to protected/private
member CBase::CBase(const CBase&)", because the second line will
try to call the copy constructor of CBase. If you write the code like :

CBase* pointer = new ( ELeave ) CBase;
CBase base;
base = *pointer; // call operator =

The
compiler will also complain because it will call the operator =
function. If you really want to do the deep copy you can write your own
public copy constructor and operator = function. The reason that CBase
do this is in most cases you will allocate some heap memory inside a
CBase derived class, and it doesn't make sense(or I can say it's
dangerous)to use the default copy constructor or default operator =
function of this kind of class. So CBase turns this feature off by
default.
Actually, in Symbian, to
provide your own public version of copy contructor or operator =
function is not a good idea neither. Because these 2 function are not
leaving functions, but the code inside these 2 functions may leave
sometimes( will call new (ELeave) or NewL() ). That's a paradox. The
good 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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐