Understanding RAII (Resource Acquisition Is Initialization) - C++
- 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).
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; }
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:~/>
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; }
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:~/>
- This time (in the following code) I have made the function
References:
Total Comments 0