Sometimes a constructor will need to allocate resources, such as memory.
The most ubiquitous case is that of a compound -class:
If a compound class contains a pointer to another -class,
it will need to allocate memory for that class during its own construction.
Note: C-classes in Symbian OS are always allocated on the heap, and always have CBase as their ultimate base class.
In the following example, CMyCompoundClass has
a data member that is a pointer to a CMySimpleClass
Here is the definition for CMySimpleClass
class CMySimpleClass : public CBase { public: CMySimpleClass(); ~CMySimpleClass(); ... private: Tint iSomeData; };
Here is the definition for CMyCompoundClass:
class CMyCompoundClass : public CBase { public: CMyCompoundClass(); ~CMyCompoundClass(); private: CMySimpleClass* iSimpleClass; // owns another C-class };
If you write the constructor for CMyCompoundClass as
follows:
CMyCompoundClass::CMyCompoundClass() { iSimpleClass = new CMySimpleClass; // WRONG }
Consider what happens when a new CMyCompoundClass is
created:
CMyCompoundClass* myCompoundClass = new( ELeave ) CMyCompoundClass;
With the above constructor, the following sequence of events occurs:
Memory is
allocated for the instance of CMyCompoundClass.
The constructor
of CMyCompoundClass is called.
The constructor
creates a new instance of CMySimpleClass and stores a
pointer to it in iSimpleClass.
The constructor completes.
But, if stage 3 fails due to insufficient memory, what happens? There
is no way to return an error code from the constructor to indicate that the
construction was not completed. The new operator will
return a pointer to the memory that was allocated for the CMyCompoundClass,
but it will point to a partially constructed object.
If we make the constructor leave, we can detect when the object was not fully constructed, as follows:
CMyCompoundClass::CMyCompoundClass()// WRONG { iSimpleClass = new( ELeave ) CMySimpleClass; }
However, this is not a viable method for indicating that an error has
occurred, because we have already allocated memory for the instance of CMyCompoundClass.
A leave would destroy the pointer to the allocated memory (this),
and there would be no way to free it, resulting in a memory leak.
The solution is to allocate all of the memory for the components of
the object, after the C++ constructor has initialized the compound object.
In Symbian OS this is performed in a ConstructL() method
by convention. For example:
void CMyCompoundClass::ConstructL()// RIGHT { iSimpleClass = new( ELeave ) CMySimpleClass; }
The C++ constructor should contain only initialization code that cannot leave (if any):
CMyCompoundClass::CMyCompoundClass() // RIGHT { // Initialization that cannot leave. }
The object is now constructed as follows:
CMyCompoundClass* myCompoundClass = new( ELeave ) CMyCompoundClass; CleanupStack::PushL( myCompoundClass ); myCompoundClass->ConstructL(); // RIGHT
This can be encapsulated in a NewL() or NewLC() method
for convenience.
Implementing two-phase construction using NewL() / NewLC()
If a compound object has a NewL() method (or NewLC()),
then this should contain both stages of construction. After the allocation
stage, the object should be pushed onto the cleanup stack before ConstructL() is
called, in case ConstructL() leaves. For example:
CMyCompoundClass* CMyCompoundClass::NewLC() { CMyCompoundClass* self = new( ELeave ) CMyCompoundClass; CleanupStack::PushL( self ); self->ConstructL(); return self; } CMyCompoundClass* CMyCompoundClass::NewL() { CMyCompoundClass* self = new( ELeave ) CMyCompoundClass; CleanupStack::PushL( self ); self->ConstructL(); CleanupStack::Pop(); // self return self; }