AceInfinity
Emeritus, Contributor
If you read up on smart pointers, you'll see the good idea behind them. Not only are they easier to use, but they also demonstrate a good model, referred to as RAII. These are wrappers such as std::unique_ptr and std::shared_ptr.
If you understand how the stack works, you'll find it much easier to understand this concept.
Say we have some code that looks similar to the following (pseudo-code):
What guarantees that the mutex will be released? This is why this is dangerous code. The function might return to the caller prematurely, in which case the mutex is never unlocked, or an exception might be thrown, in which case the mutex is not unlocked either.
In the case of an exception the stack must unwind. Let's look at some example code I threw together:
In the real world, most people would see this as pretty simple and harmless code. The fact is that we can't truly guarantee that fclose() will be called to sufficiently cleanup, and allow all of the objects on the heap to be released.
I've modified the above code to demonstrate a real world application of RAII:
What this does here is allows an object residing on the heap to maintain a pointer to the allocated object on the heap. Now, regardless of what happens, ie. the object goes out of scope in any way, or an exception is thrown here, the object's destructor will be called, which in turn calls to fclose() with the pointer given to the object which refers to the FILE pointer returned by the call to fopen().
The reason why this works is because under these circumstances outlined in the above code, the stack must unwind one way or another, and all objects residing on the stack are consequently destroyed. As shown above, we can take advantage of that. :)
If you've ever programmed in D, you may recognize this as a similar implementation of the way the scope(exit) statement works. Statements - D Programming Language.
In regards to C# or VB.NET, this is very similar to the using/Using statement.
If you understand how the stack works, you'll find it much easier to understand this concept.
Say we have some code that looks similar to the following (pseudo-code):
Code:
mutex *m = new mutex(...);
// code/function here that may throw exception.
m->release();
What guarantees that the mutex will be released? This is why this is dangerous code. The function might return to the caller prematurely, in which case the mutex is never unlocked, or an exception might be thrown, in which case the mutex is not unlocked either.
In the case of an exception the stack must unwind. Let's look at some example code I threw together:
Code:
#include <iostream>
using namespace std;
int main()
{
FILE *fp = fopen("test.out", "wb");
// code here
fclose(fp);
cout << "Never executed..." << endl;
}
In the real world, most people would see this as pretty simple and harmless code. The fact is that we can't truly guarantee that fclose() will be called to sufficiently cleanup, and allow all of the objects on the heap to be released.
I've modified the above code to demonstrate a real world application of RAII:
Code:
#include <iostream>
using namespace std;
template <class F>
struct StackObj
{
StackObj(F f) : f(f) {}
~StackObj() { f(); }
F f;
};
template <class T>
StackObj<T> CreateStackObj(T f) { return StackObj<T>(f); };
int main()
{
FILE *fp = fopen("test.out", "wb");
CreateStackObj([&fp]()
{
cout << "fclose() called" << endl;
fclose(fp);
});
try { throw; }
catch (...) {}
cout << "Never executed..." << endl;
}
What this does here is allows an object residing on the heap to maintain a pointer to the allocated object on the heap. Now, regardless of what happens, ie. the object goes out of scope in any way, or an exception is thrown here, the object's destructor will be called, which in turn calls to fclose() with the pointer given to the object which refers to the FILE pointer returned by the call to fopen().
The reason why this works is because under these circumstances outlined in the above code, the stack must unwind one way or another, and all objects residing on the stack are consequently destroyed. As shown above, we can take advantage of that. :)
If you've ever programmed in D, you may recognize this as a similar implementation of the way the scope(exit) statement works. Statements - D Programming Language.
In regards to C# or VB.NET, this is very similar to the using/Using statement.