This topic contains a summary of object lifetime idioms in Symbian C++.
In some operating systems, the object lifetime can be neglected, because the stack and heap are destroyed when a program terminates. On the Symbian platform, programs may run for months. It is therefore important that objects be cleaned up as soon as their lifetime ends, whether they are allocated on stack or heap, and whether their lifetime ended through normal processing or through an error condition.
On both stack and heap, objects have a lifetime that runs approximately as follows:
On the C stack, an object’s lifetime, for a user of that object, might look like this:
#include "s.h"
void foo() { S s; sInitialize(&s, p1,p2); sUse(&s, p3,p4); sCleanup(&s); }
Memory for the S
is allocated
on entry to the function, and de-allocated on exit. The functions sInitialize()
and sCleanup()
have been
defined as part of the API for an S
, in s.h
(in fact, C programmers are not always as disciplined
as this, and expect the users of their objects to do their own initialisation,
in an ad hoc way). The function sUse()
represents
a use of the S
. Note that the S
is
passed by pointer: its address must be taken whenever it is used as
a function parameter.
On the C heap, an object's lifetime might look like this:
void foo() { S* s=(S*)malloc(sizeof(S)); // should really check this succeeded!! sInitialize(s, p1,p2); sUse(s, p3,p4); sCleanup(s); free(s); }
This time, a pointer is used to refer to the S
: as a result, the syntax of passing an S
is slightly more pleasant, because you don’t have to take its address.
On the other hand, the allocation and de-allocation of memory
is done using malloc()
, whose syntax is extremely
ugly, and free()
.
Mostly, the lifetime of a heap-based object would not be contained within a single function like this: it might be created from one function, used from another, and destroyed from another.
One way of looking at C++ is as a neat way to control
object lifetimes. C++ allows functions to be associated directly with
objects, which means that you do not need a special naming convention
to indicate that a function is loosely associated with an object.
Two special functions are the constructor and the destructor: the
constructor is called every time the C++ system knows that an object’s
lifetime begins, and the destructor is called every time the C++ system
knows that an object’s lifetime ends. Finally, C++ defines operator new()
, which is much nicer than malloc()
, and operator delete
, which is somewhat nicer than free()
.
On the C++ stack, an object’s lifetime looks like this:
void foo() { S s(p1,p2); // invokes constructor s.Use(p3,p4); // nice syntax! } // invokes destructor
Memory is allocated on
function entry, and the constructor is invoked when processing reaches
the declaration. The use of member functions makes the syntax of using
everything much more pleasant: there is no need to pass a reference
to the S
, because that is done implicitly.
Crucially, C++ causes the destructor to be invoked when the function terminates. There is no need for the user of the class to do anything to cause this to happen — all that’s necessary is that the provider of the class provided a destructor.
Note, though, that in
some exception conditions — for instance, if the Use()
function fails in some way — the function may not return normally,
and the destructor will therefore not be invoked. We will shortly
discuss how the Symbian platform addresses this.
On the C++ heap, object lifetime looks like this:
void foo() { S* s=new S(p1,p2); // allocate, construct - should really check s->Use(p3,p4); delete s; // destruct, de-allocate }
Again, the syntax is much nicer. Only one thing cannot be provided by C++: the user of a class must still remember to delete the object at the end of its lifetime.
Symbian platform idioms for object lifetime on the stack look very similar to standard C++. The control of object lifetimes on the heap is, however, very different, as shown in the following code:
void FooL() { CS* s=new (Eleave) CS; // allocate and check CleanupStack::PushL(s); // push, just in case s->ConstructL(p1,p2); // finish constructing - might leave s->UseL(p3,p4); // use - might leave CleanupStack::PopAndDestroy(); // destruct, de-allocate }
This code fragment shows four vital things:
all heap-based
classes have names beginning with C
: they are in
fact derived from a single base class, CBase
, which
exists solely to support easy cleanup
a cleanup stack
is used to hold references to objects: if a leave occurs due to out-of-memory
or some other error, objects held on the cleanup stack are popped
from it, and destroyed. In the case of CBase*
objects
pushed to the stack, they are destroyed by calling their C++ destructor.
The CBase
class has a virtual destructor (CBase::~CBase()
) which makes this possible.
any function
which might leave is designated by a trailing L
in
its name. When you see a function that might leave, you must always ask what would happen if it did leave, and what would happen if
it did not. The operating system provides all the program infrastructure
required to allow objects to be de-allocated even when a leave occurs,
but without burdening the programmer.
new
(ELeave)
is an overloaded operator
new()
function, which will leave if it fails to allocate
the required memory. It never returns a null pointer.
Two other things are worthy of note:
since the cleanup
stack itself requires memory allocation for each stack frame, a push
might leave. The PushL()
function reflects this in
its name. The cleanup stack is guaranteed to have a free slot before
a CleanupStack::PushL()
, so that the object reference
will always be successfully stored on the stack. If a leave occurs
when allocating the next stack frame, the object will be popped and
destroyed as normal.
the C++
constructor must not leave. For objects whose construction requires
resource allocation or any other operation that might fail, this means
that construction must be separated into a C++ constructor that does
not leave, and another initialisation function that might leave, which
is conventionally called ConstructL()
.