Fundamentals of Symbian C++/Object Construction
This article discusses the Symbian C++ two phase construction pattern that is commonly used for construction of compound objects allocated on the heap.
Consider the following code that allocates an object of type CExample on the heap and assigns it to foo accordingly:
CExample* foo = new(ELeave) CExample();
The code calls the new operator, which allocates a CExample object on the heap if there is sufficient memory available. Having done so, it then calls the constructor of class CExample to initialize the object. If the CExample constructor leaves, the memory already allocated for foo and any additional memory the constructor may have allocated will be orphaned because the address of the CExample object is not assigned to foo until after construction. To avoid this, a key rule of Symbian C++ memory management is as follows:
No code within a C++ constructor should ever leave.
(A more in-depth discussion of this rule can be found at here)
However, it may be necessary to write initialization code that leaves, say, to allocate memory to store another object or to read from a configuration file that may be missing or corrupt. There are many reasons why initialization may fail, and the way to accommodate this on the Symbian platform is to use two-phase construction.
Two-phase construction breaks object construction into two parts or phases:
1. A private constructor that cannot leave.
It is this constructor that is called by the new operator. It implicitly calls base-class constructors and may also invoke functions that cannot leave and/or initialize member variables with default values or those supplied as arguments to the constructor.
2. A private class method (typically called ConstructL()).
This method may be called separately once the object, allocated and constructed by the new operator, has been pushed onto the cleanup stack; it will complete initialization of the object and may safely perform operations that may leave. If a leave does occur, the cleanup stack calls the destructor to free any resources that have already been successfully allocated and destroys the memory allocated for the object itself.
A class provides a static function that wraps both phases of construction, providing a simple and easily identifiable means to instantiate it. The function is typically called NewL() and is static so that it can be called without first having an existing instance of the class. The non-leaving constructors and second-phase ConstructL() functions are private so that a caller cannot accidentally call them or instantiate objects of the class except through NewL(). For example:
class CExample : public CBase
static CExample* NewL();
static CExample* NewLC();
~CExample(); // Must cope with partially constructed objects.
CExample(); // Guaranteed not to leave.
void ConstructL(); // Second-phase construction code, may leave.
Note that there is also a NewLC() function in class CExample. If a pointer to an object is pushed onto the cleanup stack and remains on it when that function returns, the Symbian C++ convention is to append a C to the function name. This indicates to the caller that, if the function returns successfully, the cleanup stack has additional pointers on it.
Typical implementations of NewL() and NewLC() are as follows:
CExample* self = new (ELeave) CExample(); // First-phase construction.
self ->ConstructL(); // Second-phase construction.
CExample* self = CExample::NewLC();
The NewL() function is implemented in terms of the NewLC() function rather than the other way around (which would be slightly less efficient because this would require an extra PushL() call on the cleanup stack).
Each function returns a fully constructed object, or will leave if there is insufficient memory to allocate the object (that is, if operator new(ELeave) leaves) or if the second-phase ConstructL() function leaves for any reason. This means that, if an object is initialized entirely by two-phase construction, the class can be implemented without the need to test each member variable to see if it is valid before using it. If the object exists, it has been fully constructed.
The result is an efficient class implementation without a test against each member pointer before it is de-referenced.
A destructor may be called to clean up partially constructed objects if a leave occurs in the second-phase ConstructL() function described in the above section. The destructor code cannot assume that an object is fully initialized and should not call functions on pointers that may not point to valid objects. For example:
if (iPointer) // iPointer may be NULL.
A leave should never occur in a destructor or in cleanup code. One reason for this is that a destructor could itself be called as part of cleanup following a leave. A further leave during cleanup would be undesirable, if nothing else because it would mask the initial reason for the leave. More obviously, a leave within a destructor would leave the object destruction incomplete and may leak resources.
It is important to consider the consequences of deleting an object referred to by a class member variable pointer. If the object of the owning class continues to exist, what does the pointer refer to? If the object has been deleted, it points to memory which looks valid but has been released back to the heap. If a destructor call occurs, the destructor attempts to destroy the memory again, and a panic occurs.
To avoid this, when a member variable is deleted, a common programming technique is to set it to zero before instantiating a new object to replace it. If the allocation fails, at least the destructor, when it is called later, will not try to delete the initial object again. For CExample:
delete iPointer; // Destroy old member variable.
iPointer = NULL;
iPointer = CDerivedPointer::NewL(); // Create a new one.
However, this causes a problem for the implementation of CExample, since if RenewMemberL() fails, the iPointer member is NULL. If RenewMemberL() is a public method, the class cannot guarantee that iPointer is valid, since it may have been deleted but the replacement allocation may have failed. This means that iPointer must be tested before use, which runs contrary to the benefits of two-phase construction described previously, that is, that all member pointers are valid if an object exists.
A solution is to write RenewMemberL() to allow rollback:
CPointer* tempPtr = CDerivedPointer::NewL(); // Create a new pointer.
delete iPointer; // Destroy old member variable.
iPointer = tempPtr;
If the allocation of the new member fails, RenewMemberL() leaves, but the original iPointer member is retained. The caller of RenewMemberL() receives the error and can decide whether to continue to use the object with the original member, or to otherwise handle the error.
(Note that it is never necessary to NULL a member variable if the deletion occurs within a destructor itself).
Since CBase’s new operator zeros memory, a member variable pointer should always be NULL or pointing to a valid object (providing idioms such as RenewMemberL() above are followed).
Stack Based Objects
From Symbian OS v9.1 it is possible for stack-allocated objects (ie T classes) to have a destructor that is called in the event of a Leave.
Note however that it is important that stack based classes should not themselves have leaving destructors (actually this is good advice for any class!). Its also important to note that if you give a T class a destructor then you risk its mis-use if its allocated on the heap.
Both implications are discussed in the topic The Implications of Leaving in a Destructor.