LinuxQuestions.org
Share your knowledge at the LQ Wiki.
Home Forums Tutorials Articles Register
Go Back   LinuxQuestions.org > Blogs > Aquarius_Girl
User Name
Password

Notices


Rate this Entry

Understanding RAII (Resource Acquisition Is Initialization) - C++

Posted 06-14-2012 at 01:45 AM by Aquarius_Girl
Updated 10-09-2012 at 02:44 AM by Aquarius_Girl

  • Q: What is an exception safe code?
    A: A code may throw an exception but at the same time if it:
    • Doesn't allow any memory (allocated by it) to be leaked.
    • Lets the program to remain in a well-defined state (no dangling pointers
      and all) so that execution can continue as intended.
    • Reports the error to the caller (when encountered).
    then it can be termed as an exception safe code.

    Of course, a code which won't be throwing any exception ever is an exception
    safe code.

    One way to write an exception safe code would be to write the clean up
    logic manually everywhere.


    Code:
    #include <iostream>
    static int holder;
    
    /* Our own exception classes - just for fun. */
    class myExceptionClassA 
    {
        public:
        myExceptionClassA () {std::cout << "\nWarning: Division by zero number isn't allowed.\n";}
    };
    
    class myExceptionClassB
    {
        public:
        myExceptionClassB () {std::cout << "\nWarning: Division by dividend isn't allowed.\n";}
    };
    
    /* This function:
        * Allocates the memory for a local integer variable.
        * Throws above defined exceptions on the specified cases.
        * Returns the division result.
        * Deallocates the allocated memory.
    */
    int doDivide (int toBeDividedBy) throw (myExceptionClassA, myExceptionClassB)
    {
        /* The pointer usage isn't necessary here, its done for the demonstration purpose. :)*/
        int *result = new int;
        *result     = 200000;
        
        /* If the divisor is 0, then throw an exception.*/
        if (toBeDividedBy == 0)
        {
            /* The remaining code won't get executed when the exception is thrown, so it is 
    necessary for us to manually delete the allocated memory in order to prevent the memory leaks. */
            delete result;
            throw myExceptionClassA ();
        }
        /* If the divisor is same as dividend, then throw an exception. */ 
        else if (toBeDividedBy == *result)
        {
            /* The remaining code won't get executed when the exception 
    is thrown, so it is necessary for us to manually delete the allocated memory in order to prevent 
    the memory leaks. */
            delete result;
            throw myExceptionClassB ();
        }
    
        /* The following code won't get executed if/when an exception is thrown. */
        std :: cout <<"\nException wasn't thrown. :)\n";
        
        *result = *result / toBeDividedBy;
        
        /* Before the function returns we need to delete the allocated 
    memory in order to prevent the memory leaks. We also need to return its result, so here we have held 
    the `result` variable's value in a static variable `holder` (file scope), and then deleted it. */
        holder = *result;
        
            delete result;
        return holder;
    } 
    
    int main ()
    {
        try
        {
            std :: cout << doDivide (200000)<< "\n";
        }
        catch (myExceptionClassA) {}
        catch (myExceptionClassB) {}
        
        try
        {
            std :: cout << doDivide (3)<< "\n";
        }
        catch (myExceptionClassA) {}
        catch (myExceptionClassB) {}
        
        try
        {
            std :: cout << doDivide (0)<< "\n";
        }
        catch (myExceptionClassA) {}
        catch (myExceptionClassB) {}
        
        try
        {
            std :: cout << doDivide (4)<< "\n";
        }
        catch (myExceptionClassA) {}
        catch (myExceptionClassB) {}
        
        return 0;
    }
    
    Output:
    Code:
    anisha@linux-y3pi:~/> g++ exceptionSafe2.cpp -Wall
    anisha@linux-y3pi:~/> valgrind ./a.out
    ==7680== Memcheck, a memory error detector
    ==7680== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
    ==7680== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
    ==7680== Command: ./a.out
    ==7680== 
    
    Warning: Division by dividend isn't allowed.
    
    Exception wasn't thrown. :)
    66666
    
    Warning: Division by zero number isn't allowed.
    
    Exception wasn't thrown. :)
    50000
    
    ==7680== 
    ==7680== HEAP SUMMARY:
    ==7680==     in use at exit: 0 bytes in 0 blocks
    ==7680==   total heap usage: 6 allocs, 6 frees, 274 bytes allocated
    ==7680== 
    ==7680== All heap blocks were freed -- no leaks are possible
    ==7680== 
    ==7680== For counts of detected and suppressed errors, rerun with: -v
    ==7680== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)
    anisha@linux-y3pi:~/>
    Observation:
    From the above code I can conclude that until and unless I or the
    other programmer who'll be taking up charge after me is very careful,
    this program "can" result in memory leaks.

  • Q: What's the meaning of the smart term - "Resource Acquisition Is Initialization"?
    A: 'Acquisition' means 'acquiring'.
    The term means that when we initialize an 'object', we acquire
    a resource (e.g. memory, or locks (w.r.t threads)). Memory is acquired
    through the API like `new`, `malloc` etc., and freed with
    API like `delete` and `free` in C++/C.

    C++ provides the support for the exception handling, C doesn't.
    In C++, when you throw an exception from a function, the control
    goes out of the function and therefore the code below the throw
    statement never gets executed.

    So, if we acquire some resource after the throw statement we won't
    be ever able to release it if the exception gets actually thrown!

    C++ provides 'destructors'. Destructors are the only functions that
    get executed once an exception is thrown and/or the control goes
    out of the scope anyhow.
    So, if we write the resource releasing code in the destructors we
    won't have to worry about the memory leaks.

    In a nutshell, the point here is that we should acquire a resource
    by initializing an object whose 'destructor' will release the resource
    acquired after its(object's) life is over.

    So, another way to write an exception safe code would be to let the
    destructor do the job of "cleaning up" rather than doing it ourselves.


    Humans can forget, destructors can't.
    • This time (in the following code) I have made the function
      `doDivide` a class member.
    • The variable `result` is now a private member of the class.
    • The memory deallocation now happens "only" in the destructor.

    Code:
    #include <iostream>
    
    /* Our own exception classes - just for fun. */
    class myExceptionClassA 
    {
        public:
        myExceptionClassA () {std::cout << "\nWarning: Division by zero isn't allowed.\n";}
    };
    
    class myExceptionClassB
    {
        public:
        myExceptionClassB () {std::cout << "\nWarning: Division by dividend isn't allowed.\n";}
    };
    
    /* `doDivide` is a member function of the class `divisionClass` now. */
    class divisionClass
    {
        private:
        /* Since the variable result is a class member now, we can return 
    it safely from the function `doDivide`. There isn't any need for any static member now for holding 
    the value of the variable `result` as we used in the previous example for returning from the 
    function `doDivide`. */
        int *result;
        
        public:
        divisionClass () 
        {
            /* Allocating memory to the private variable in the constructor. */
            result = new int;
        }
    
        /* 
            The class function `doDivide`:
            * Throws above defined exceptions on the specified cases.
            * Returns the division result.
            * Does NOT handle the deallocation of memory anymore now.
        */
        int doDivide (int toBeDividedBy) throw (myExceptionClassA, myExceptionClassB)
        {
            *result       = 200000;
            
            /* If the divisor is 0, then throw an exception. */
            if (toBeDividedBy == 0)
            {
                throw myExceptionClassA ();
            }
            /* If the divisor is same as dividend, then throw an exception. */
            else if (toBeDividedBy == *result)
            {
                throw myExceptionClassB ();
            }
    
            /* The following code won't get executed if/when an exception is thrown.  */
            std :: cout <<"\nException wasn't thrown. :)\n";
            
            *result = *result / toBeDividedBy;
            return *result;
        }
        
        /*Deallocation of the memory of the members of this class is now 
    the job of the destructor. */
        ~divisionClass () 
         {
            std::cout << "\nDestructor being called.\n";
            delete result;
         }
    };
    
    int main ()
    {
        try
        {
            /* This object's scope is limited within this {} block of try. */
            divisionClass obja;
            std :: cout << "\n": obja.doDivide (200000) << "\n";
        }
        /* When the object goes out of the scope, the destructor is bound to get called. */
        
        catch (myExceptionClassA) {}
        catch (myExceptionClassB) {}
        
        try
        {
            divisionClass objb;
            std :: cout << objb.doDivide (3) << "\n";
        }
        catch (myExceptionClassA) {}
        catch (myExceptionClassB) {}
        
        try
        {
            divisionClass objc;
            std :: cout << objc.doDivide (0) << "\n";
        }
        catch (myExceptionClassA) {}
        catch (myExceptionClassB) {}
        
        try
        {
            divisionClass objd;
            std :: cout << objd.doDivide (4) << "\n";
        }
        catch (myExceptionClassA) {}
        catch (myExceptionClassB) {}
        
        return 0;
    }
    Output:
    Code:
    anisha@linux-y3pi:~/> valgrind ./a.out 
    ==7630== Memcheck, a memory error detector
    ==7630== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
    ==7630== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
    ==7630== Command: ./a.out
    ==7630== 
    
    Warning: Division by dividend isn't allowed.
    
    Destructor being called.
    
    Exception wasn't thrown. :)
    
    Case: Division by 3 - 66666
    
    Destructor being called.
    
    Warning: Division by zero isn't allowed.
    
    Destructor being called.
    
    Exception wasn't thrown. :)
    
    Case: Division by 4 - 50000
    
    Destructor being called.
    ==7630== 
    ==7630== HEAP SUMMARY:
    ==7630==     in use at exit: 0 bytes in 0 blocks
    ==7630==   total heap usage: 6 allocs, 6 frees, 274 bytes allocated
    ==7630== 
    ==7630== All heap blocks were freed -- no leaks are possible
    ==7630== 
    ==7630== For counts of detected and suppressed errors, rerun with: -v
    ==7630== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)
    anisha@linux-y3pi:~/>

References:
Views 2014 Comments 0
« Prev     Main     Next »
Total Comments 0

Comments

 

  



All times are GMT -5. The time now is 11:04 PM.

Main Menu
Advertisement
Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
Open Source Consulting | Domain Registration