Introduction
The articles main objective is to educate the newbie's how to code a memory leak free program in c++.
Its been a long time since I am thinking of writing an article on auto_ptr's. When I started learning auto_ptr's I found many articles describing what auto_ptr's really are, what are their uses and how to implement code using them. To be very frank I could't find any article which explains auto_ptr's in depth. Finally I have decided to write an article which will serve as an extention to the articles available on the web.
To start with auto_ptr's, the very first obvious question one will ask is what are auto_ptr's?.
Auto pointers ship as a part of STL's standard namespace. auto_ptr is a template class created mainly for encapsulating any dynamically created object preferably using new operator. Auto pointer's acts as a container for dynamically created objects on the free store, once you allocate a memory block you need not really care about deallocating memory because auto pointer takes care of freeing that memory area once the encapsulated object goes out of scope & probably not needed anymore.
Let's have a look at the unsafe code below:
void myFunction()
{
T* ptr = new T;
delete ptr;
}
Most people like us write code like this everyday, this code will work very well if it doesn't do anything exceptional. What if myFunction never executes the delete statement, either because of a condition which causes the control to go back to the caller as a result of the function return or because of an exception thrown during the execution of the function. This is an ideal scenario where we can have a classic memory leak problem.
Now the very first question that will popup in your mind is how to handle such difficult situations. The answer to this problem is auto pointer, we can just wrap the pointer ptr declared in the code above into a smart pointer that is auto pointer object and forget about the memory leak problems. Now even if the code execution doesn’t reach to the delete statement we can make sure that the memory pointed by ptr variable gets cleaned up when the function’s stack unwinds.
Let’s have a look at the corrected myFunction function:
#include <memory>
using namespace std;
void myFunction()
{
auto_ptr ptr( new T );
}
In the above code we need not even have to explicitly call delete on the ptr’s contained pointer. The auto pointer automatically frees up the memory of the contained object on destruction.
Using an auto_ptr is just the same as using built-in pointer, and to "take back" the resource and assume manual ownership again, we just call release():
class CMyClass
{
private:
int m_nData;
public:
CMyClass()
{
m_nData = 0;
}
CMyClass(int nData)
{
m_nData = nData;
}
void SetValue(int nData)
{
m_nData = nData;
}
void PrintValue()
{
printf("Value is: %d\n",m_nData);
}
CMyClass& operator =(int nData)
{
m_nData = nData;
return *this;
}
};
void myFunction()
{
CMyClass* ptr1 = new CMyClass();
auto_ptr ptr2( ptr1 );
*ptr2 = 12;
ptr2->PrintValue();
assert( ptr1 == ptr2.get() );
CMyClass* ptr3 = ptr2.release();
delete ptr3;
}
We can use auto_ptr's reset() function to reset the auto_ptr to own a different object. If the auto_ptr already owned an object, though, it first deletes the already-owned object, so calling reset() is much the same as destroying the auto_ptr and creating a new one that owns the new object:
void myFunction()
{
auto_ptr ptr( new CMyClass(1) );
ptr.reset( new CMyClass(2) );
}
Now comes the main part that I discussed in the begining of this article, I have not seen any real world example describing use of auto_ptr as a class level variable.
class CName
{
public:
CName()
{
m_pName = NULL;
}
~CName()
{
delete[] m_pName;
}
void SetupName(int nSize)
{
m_pName = new char[nSize];
}
void SetName(char* pName)
{
strcpy(m_pName,pName);
}
char* GetName()
{
return m_pName;
}
private:
char* m_pName;
};
class CEmployee
{
public:
CEmployee()
{
m_ptrName = new CName();
}
~CEmployee()
{
delete m_ptrName;
}
void Name(char* pName)
{
m_ptrName->SetName(pName);
}
void PrintName()
{
printf("Name: %s\n",m_ptrName->GetName());
}
private:
CName* m_ptrName;
};
void myFunction()
{
CEmployee objEmp;
objEmp.Name("Neeraj");
objEmp.PrintName();
}
The code above will definitely cause a runtime exception as when setting employees name memory is not allocated by calling SetupName function. Its certain that the program execution stops whenever an exception occurs, if you notice the code carefully there is a classic memory leak in there as class CEmployee creates CName’s instance using operator new in its constructor. This allocated memory never gets freed up as the code execution never reaches upto this point. We can certainly make the memory allocation exception safe by encapsulating the CName's ptr into an auto_ptr object, following code ensures depicts the correct code of CEmployee class.
class CEmployee
{
public:
CEmployee()
{
m_ptrName = auto_ptr(new CName());
}
~CEmployee()
{
CName* ptr = m_ptrName.release();
delete ptr;
}
void Name(char* pName)
{
m_ptrName->SetName(pName);
}
void PrintName()
{
printf("Name: %s\n",m_ptrName->GetName());
}
private:
auto_ptr m_ptrName;
};
The code above creates a auto_ptr object member of CEmployeeName class. By doing this we are ensuring that the memory contained by the m_ptrName object gets freed up even in case an exception occurs. Look carefully at the constructor & destructor, the class's constructor creates an instance of CName class and passes on the ownership to the class's member CName auto_ptr m_ptrName. In the destructor we have taken back the ownership of auto_ptr's contained CName class's pointer which we have deleted safely by calling delete.
When one auto_ptr's ownership is passed from one auto_ptr to another. If the lvalue auto_ptr object already contains a valid ptr then the auto_ptr first releases the memory it holds and then attaches the rvalue ptr to it.