Sunday, March 27, 2011

Step-motherly treated C++ feature : Smart Pointers

        
     Raw pointers pointing to location acquired from free store must always return the resources to the free store, else the classical problem of memory leak prevails. Let's work on the issues with the following code snippet -
void foo()
{
    int *rawPtr = new int(100) ;
    
    // Some functionality involving complex code

    delete rawPtr ;
}
        There are at least three situations, which I can think of, where the above code would fail to deallocate the resources "rawPtr" is pointing to.
  1.     What if an exception rise in the complex part of the code ?
  2.     What if there is an early return in the complex part of the code ?
  3.     What if you accidentally placed another delete statement on rawPtr ?
        In first two situations, statement "delete rawPtr ;" never executes and there is memory leak which we never expected to have. Deleting a dangling pointer behavior is undefined and happens in the last case. C++ programmers should always take the advantage of smart pointers: std::auto_ptr to handle the raw pointers, which is the simplest smart pointer in the family. Now lets work on same snippet using std::auto_ptr which is available in the standard header "memory" and defined in the "std" namespace.
void foo()
{
    int *rawPtr = new int(100) ;
    std:: auto_ptr<int> safePtr(rawPtr) ;

    (*safePtr) = 50 ;   // This is same as (*rawPtr) = 50 ;
   
    // Some functionality involving complex code

} // safePtr out of scope
What actually is the statement doing: std:: auto_ptr<int> safePtr(rawPtr) ;
       
        "safePtr" is an auto pointer parameterized on int type, signifying the capability of owning the address of location, that can hold an int, acquired from free store. Here it takes the ownership of the raw pointer, "rawPtr",  pointing location. In any case if the "safePtr" goes out of scope, it returns the resource it owns to the free store safely avoiding memory leak. Notice that, there is no need to explicitly mention to deallocate the resources using the "delete" statement. Using "delete" statement turns down the whole purpose of using smart pointers. In fact, the whole snippet can me minimized avoiding the usage of raw pointers at all.
void foo()
{
    std:: auto_ptr<int> safePtr(new int(100)) ;

    (*safePtr) = 50 ;
    // Some functionality

} // safePtr out of scope
The same can be done on user-defined types too.
class foo
{
    std:: auto_ptr<int> safeMember ;
    public:
        foo( int number=10 ) : safeMember( new int(number) )
        {}
        inline int getNum() const
        {
            return (*safeMember) ;
        }
};

int main()
{
    std:: auto_ptr<foo> safePtr( new foo ) ;  // foo is a user-defined type
    std:: cout<< safePtr->getNum() << "\n" ;
                     // ^^ Notice the use of member access operator -> on safePtr
    return 0 ;
}
    release
         release() releases the ownership of the memory location std::auto_ptr owning and returns a pointer to the location. In C++, it's the responsibility of the caller to catch the return value. It is completely valid not to collect the return value of release() but at the cost of never being able to resolve the memory leak issue.
void foo()
{
    
    std:: auto_ptr<foo> safePtr( new int(100) ) ;
    int *rawPtr = safePtr.release() ;
    delete rawPtr ;  // Definitely necessary

}  //  safePtr goes out of scope but does nothing like deallocation since the resources are released
    reset
        reset() resets the ownership of std::auto_ptr to take the ownership of new location. So, when an std::auto_ptr is reinitialized/reassigned to take the ownership of new location -
  •     It first deallocates the resources, if at all it owns anything
  •     Then, takes the ownership of new location.
void foo()
{
    std:: auto_ptr<foo> safePtr( new int(100) ) ;  // safePtr owns memory location, say M1
    safePtr.reset( new int ) ;   // safePtr now owns memory location, say M2
    safePtr.reset() ;
}
Pitfalls
    
    1.    Assignment operation is similar to a reset() operation, in this snippet. It first deallocate the resource it owns and takes the ownership of new resource. Here, the assignment operation results "rawPtr" dangling.
void foo()
{

    int *rawPtr = new int(100) ;
    std:: auto_ptr<foo> safePtr(rawPtr) ;

    int *anotherRawPtr = new int(50) ;
    safePtr = anotherRawPtr ;  // Assignment operation

    (*rawPtr) = 25 ;  //  Undefined behavior

}  // safePtr out of scope
    2.    Should be careful while passing a raw pointer to a function whose argument is of type std::auto_ptr and return type is void.
void foo(std:: auto_ptr<foo> safePtr)
{
    
    // ....

}  // safePtr goes out of scope and causes to deallocates the argument's resource it owned

int main()
{
    
    int *rawPtr = new int(100) ;
    foo(rawPtr) ;  // Upon return of foo, causes rawPtr dangling
    (*rawPtr) = 50 ;  // Undefined behavior

}
Conclusion

        Why should a programmer think of memory leaks when the run time is capable of dumping them after program termination? With memory leaks, program is laying off memory locations on a free way which no other process can have access to until the program's termination. Besides that it is not a good programming practice to rely on run time when programmer can efficiently handle the situations. Smart pointers are useful for the purpose and “std::auto_ptr” should be used to handle raw pointers. It provides an extremely safe way of avoiding memory leaks in any unwarranted situations. They deallocate the resources automatically ( of course nothing is automatic and some thing on our behalf is going behind the scenes ) they are owning once they are out of scope and the name aptly suits justifying it.

PS: One can easily check for memory leaks on a Visual Studio environment in Debug mode. Compiled the below snippet on Visual C++ 2010 Express Edition.

#define _CRTDBG_MAP_ALLOC

#include <crtdbg.h>

int main()
{
    
    int *rawPtr = new int(100) ;
    // Purposefully not deallocating the rawPtr owned resources from free store
    _CrtDumpMemoryLeaks() ;
    return 0;

}
Output:
....
Detected memory leaks!
Dumping objects ->
{56} normal block at 0x006C3250, 4 bytes long.
Data: <2 > 32 00 00 00
Object dump complete.
The program '[6160] blogPostMemoryLeaks.exe: Native' has exited with code 0 (0x0).

1 comment:

  1. Disclaimer: Article posted here might have wrong information and request the readers to comment on the mistakes for the further improvement of present and future articles.

    ReplyDelete