Introduction to why Symbian constructors must never leave
and how Symbian achieves object initialization using constructors
and second-phase ConstructL()
methods.
In standard C++ the new
operator throws an std::bad_alloc
exception if
insufficient heap memory is available. It is also possible to use new (nothrow)
to indicate that a null pointer should be
returned instead of throwing an exception.
Symbian uses the
trap and leave mechanism instead of exceptions. Accordingly new (ELeave)
calls an overload of operator new()
that indicates failure through a leave instead of an exception.
The leave code is KErrNoMemory
(-4). ELeave
is a constant value of the enumeration TLeave
,
defined in e32const.h.
new (ELeave)
:CExample* ptr = new (ELeave) CExample;This code can be broken down into 3 steps:
Allocate memory.
In the case of a C object derived from CBase
the
allocation function is TAny* CBase::operator new(TUint aSize,
TLeave)
.
Invoke the constructor. The default constructor is called in this example.
Return the address
of the initialized object. This is stored by the caller in the pointer ptr
.
KErrNoMemory
leave
would result and control would transfer to the enclosing trap. In
this case no memory has been allocated so no memory leak would occur.What if a leave occurs at step 2 in the CExample
constructor? This would cause a problem because the memory allocated
at step 1 would leak. The calling code cannot be expected to resolve
this problem using the cleanup stack because the address of the object
has not yet been returned to the calling code.
The second phase constructor
// Static member function to perform object creation // using the two-phase construction idiom. CExample* CExample::NewL() { // Allocate the object. A leave could occur when // the memory for the object is allocated, but // not as a result of the constructor call. CExample* self = new (ELeave) CExample; // Push the address of the object onto the cleanup stack. CleanupStack::PushL( self ); // Perform any initialization that can leave. self->ConstructL(); // Remove the object from the cleanup stack now that it is // safely initialized. CleanupStack::Pop(); // Return the address of the fully initialized object. return self; }
ConstructL()
is the conventional name
for the function that is invoked to carry out any initialization of
the object that can leave. This function is known as the second phase
constructor. ConstructL()
is only called after the
address of the object has been pushed onto the cleanup stack. If a
leave were to occur in ConstructL()
the cleanup stack
would ensure that the partially constructed object is destroyed.Classes that use two-phase construction often
restrict access to the constructors and ConstructL()
functions by using the protected
access modifier.
The user instantiates the class using a public, static factory function
called NewL()
, as in the example above. All the arguments
required to initialize the object are passed to NewL()
. Inside NewL()
some of these arguments are passed
on to the constructor, others to ConstructL()
.
NewL()
is no
longer on the cleanup stack. If you store the address in a local variable
you must push it onto the cleanup stack before it is safe to call
a leaving function. For example:// Use the static factory function to create the // object. Internally this uses two-phase construction. CExample* ptr = CExample::NewL(); // Push the pointer onto the cleanup stack so that it is // possible to call a leaving function safely. CleanupStack::PushL( ptr ); // Invoke a leaving function. ptr->UseL();Classes that define a
NewL()
function usually also define NewLC()
as a convenience.
The only difference between NewL()
and NewLC()
is that the address returned by NewLC()
is already
on the cleanup stack. So the following is equivalent to the code above:// Use the factory function that creates the object // and returns the address already on the cleanup stack. CExample* ptr = CExample::NewLC(); // Invoke a leaving function. ptr->UseL();