Showing posts with label constructors. Show all posts
Showing posts with label constructors. Show all posts

Wednesday, 15 August 2007

Zombie

This article talks about handling a failing constructor.

What does it mean when I say Failing Constructor?

Consider a situation where we have written a constructor for a class and for some reasons it fails before fully executing the constructor. Constructor wont return any value, so it wont be possible for us to tell whether the constructor is fully executed or not.

What's the state of the object in this case?

Its a zombie object, because of the reason that the construction of the object has not happened in the expected way.

What's the problem with zombie objects?

Constructor has not executed completely, it has ended unexpectedly. This means the object has not been constructed. Hence, Destructor for the same object wont be executed. We will encounter problem, If we have done any dynamic allocations or opened any resource handles in the constructor before it fails, because the memory allocated wont be freed since the code for freeing up the memory or closing the resource handles usually sits in the destructor.

Code below explains this situation,

class CTest
{
public:
    // Constructor
    CTest();

    // Destructor
    ~CTest();

private:
    // Member pointer variables
    char *m_ptr1;
    char *m_ptr2;
};

// Constructor definition
CTest::CTest()
{
    // Init member pointers
    m_ptr1 = m_ptr2 = NULL;

    m_ptr1 = new char[100]; // Assume this succeeds

    m_ptr2 = new char[100]; // Assume this fails here!

    // Other processing

    ...
}

// Destructor definition
CTest::~CTest()
{
    delete [] m_ptr1;
    delete [] m_ptr2;
}

Have a look at the constructor CTest::CTest(), assume the first dynamic allocation succeeded for m_ptr1 and m_ptr2 failed for some reasons. Since the object is not constructed fully destructor wont be called and hence the heap allocations are not freed.

How do we overcome this problem?

One solution would be to catch the exception, free the allocated memory and then re throw the exception. Following code will show this technic.

// Constructor definition
CTest::CTest()
{
    // Init member pointers
    m_ptr1 = m_ptr2 = NULL;

    // try block
    try
    {
        m_ptr1 = new char[100]; // Assume this succeeds

        m_ptr2 = new char[100]; // Assume this fails here!

        // Other processing

        ...
    }
    // catch block
    catch(...)
    {
        // free the resources
        delete [] m_ptr1;
        delete [] m_ptr2;

        // re-throw
        throw;
    }
}

Another solution would be to use smart pointers, instead of just the raw pointers. When exception is thrown, the destructors for the local variables will be called as part of stack-unwinding which calls the destructor of the smart pointer which in turn frees the resources.

Enjoy C++!

Wednesday, 8 August 2007

Overloading "new"

In one of the previous article about operator new, we saw that it can always be overloaded.

operator new can be overloaded to define its own way of allocating memory for an object. User should be able to control the way how it allocates the memory for the object.

The global new operator is the one which invokes a call to appropriate version of operator new if its overloaded and then it will call the constructor to complete the object instantiation process.

Below is the pseudo code of what new operator does

new()
{

    // Call correct version of operator new() based on the parameters passed while initializing the object
    operator new(sizeof(CMyClass));

    // Call appropriate version of constructor
    CMyClass::CMyClass();    

}

Function operator new always has size_t as one of its parameter. The functionality of a default operator new will be as shown below,

void* operator new(size_t size)
{
    // allocate and return the pointer
    return malloc(size); 
}

Where it allocates size number of bytes and return the pointer which points to the allocated memory.

If user tries to instantiate an object of type CMyClass dynamically,

CMyClass *pObj = new CMyClass();

This will call the global new operator() function which will first call operator new(sizeof(CMyClass)) passing sizeof(CMyClass) as parameter to allocate required memory, then it will call the constructor CMyClass::CMyClass() to finish the object instantiation process.

In some cases user may want to allocate memory for an object in a predefined memory area. This can be achieved by overloading operator new().

void* operator new(size_t size, void *pMem)
{
    // just return the pre allocated memory pointer
    return pMem; 
}

User can invoke this by doing the following,

char *buf  = new char[500];                //pre-allocated buffer
CMyClass *pObj = new (buf) CMyClass();    //placement new

This will invoke the overloaded version of function operator new() which takes two parameters, first would be the size of the class and the next would be the pointer to pre-allocated buffer.

operator new(sizeof(CMyClass), buff);

And this overloaded version will just return the pointer to new operator() function which will then call the appropriate constructor.

This technic is popularly known as Placement New.

In the same way user should be able to overload operator new with different parameters.

Cheers!

Tuesday, 12 June 2007

Conceptual "Virtual Constructors"

In one of my old article I had said that we cannot make the constructors virtual.

But its possible to have Conceptual Conceptual "Virtual Constructors". You might be wondering what do I mean by conceptual here?

Consider following code snippet,


class CAccountIface
{

};

class CSavingsAccount : public CAccountIface
{

};

class CCreditAccount : public CAccountIface
{

};

 


And I have one more class which will have a list of accounts


class CBank
{
    public:
        CAccountIface* CreateAccount(int iAccType);
        std::vector<CAccountIface *> m_Accounts;
}


 Here the member function CAccountIface* CBank:: CreateAccount(int iAccType) acts like a virtual constructor. Depending on the type the user passes either the constructor for CSavingsAccount or CCreditAccount will be called and respective object will be instantiated. This behaves like a virtual constructor.


We can even establish virtualism in case of copy constructors.

Let's try writing a copy c'tor for CBank class.


CBank::CBank(const CBank &AnotherBank)
{

    for(std::vector<CAccountIface *>::size_type i = 0; i < AnotherBank.m_Accounts.size(); ++i )
    {
        this->m_Accounts[i] = AnotherBank.m_Accounts[i]->Clone();
    }
}


I guess u'll already be wondering about the implementation for clone() member function.

Clone() has to be a pure virtual function in CAccountIface, and we can override this in CSavingsAccount and CCreditAccount to clone itself.

So the updated code will be as shown below,


class CAccountIface
{
    public:
        virtual CAccountIface *Clone() = 0;
};

class CSavingsAccount : public CAccountIface
{
    public:
        virtual CSavingsAccount *Clone(){ return new CSavingsAccount(*this);}

};

class CCreditAccount : public CAccountIface
{
    public:
        virtual CCreditAccount *Clone(){ return new CCreditAccount(*this);}

};


When we say


m_Accounts[i]->Clone();


The Clone() function will be called based on the type of object pointed by the pointer m_Accounts[i].

This concept is also referred to as "Factory Pattern"

Wednesday, 14 March 2007

Calling ctor!!

Well, suppose we have a class as below


class CTest
{
    public:
        CTest()
        {
            cout<<"ctor called!";
        }
};

int main()
{
    CTest();
}


Does it mean c'tor can be called without creating an object?

Nope!! ofcourse not!!

The reason is when CTest(); is called, the compiler inserts the code to create a tempory object of CTest and calls its c'tor. Also it destroys the object right after executing CTest(); statement.

That is the reason calling constructor inside another constructor is not valid.