Self Destruction.
Although it sounds counter-intuitive self destruction can be an incredibly useful thing. When I say self destruction I mean, of course, an object's ability to call a destructor to tidy up any resources it may have used during its lifetime. To demonstrate this let's look at my new best friend, the smart pointer. In its most basic form it looks a bit like this (plus overloads for the dereference operators) :
template <class T>
class SmartPtr
{
public:
SmartPtr(T* rawPtr = 0) : m_ptr(rawPtr){};
~SmartPtr(){delete m_ptr;};
private:
T* m_ptr;
};
Ultimately you have a raw pointer wrapped in a class, the gist of it being this: whenever a SmartPtr object goes out of scope and the destructor is called it guarantees that the memory allocated from the heap is released via a call to delete. This is not only useful for preventing those times when you just plain forget to call delete when using raw pointers, it also handles cases like this:
MyClass* ptr = new MyClass();
ptr->DoStuff();
delete ptr;
This looks OK on the surface, but who knows where DoStuff() goes, or how long it'll take to return. At any point it could throw an exception and this is where we come across a problem: if the exception is unhandled and the program bails then delete will never be called causing a resource leak. On the other hand, using SmartPtr makes things more manageable:
SmartPtr<MyClass> ptr = SmartPtr<MyClass>(new MyClass());
ptr->DoStuff();
Immediately noticeable is the lack of a call to delete - this is because now that it is in the SmartPtr destructor it is guaranteed to be called, either when the SmartPtr goes out of scope or if an exception is thrown. In fact trying to call delete on a SmartPtr object would be syntactically incorrect.
Of course I'm only reiterating the fundamental use of smart pointers, this is information which can be found anywhere there is discussion of dynamic memory management in C++, and I mention this only to highlight one of the uses of the destructor.
To highlight another use the next example, from Effective C++, shows how the destructor comes into play when using a threading library. When accessing shared resources from multiple threads it is common practice to use a mutex to lock a resource while a thread accesses it. Once it has been accessed the mutex needs to be unlocked again to make the resource available to the other threads.
mutex.lock();
DoStuffToSharedResource();
mutex.unlock();
Already there are striking similarities to the smart pointer example, only this time we are calling lock() and unlock() rather than new and delete respectively, and the ramifications of failing to call unlock(), while not similar, could be considered just as dire as not calling delete on a pointer. It is only a short jump, then, to creating a Lock class which works a bit like a smart pointer.
class Lock
{
public:
Lock(Mutex& mutex) : m_mutex(mutex){mutex.lock();};
~Lock(){m_mutex.unlock();};
private:
Mutex& m_mutex;
};
Here the mutex is passed to the Lock class constructor which stores a reference to it and calls its lock() function. The destructor then takes care of making sure the mutex is unlocked when the lock object goes out of scope. Once again it also makes the example code shorter:
Lock lock(mutex);
DoStuffToSharedResource();
So useful is this, that it is exactly how the Lock class works in SFML (the threading classes in SFML are, in fact, very useful in general, for which I can vouch after having made use of them in sfchat).
So there you have two good examples of common practices which take advantage of class destructors. A final example is a practical use I deployed myself, since having learned the above methods. When working on a particular project I thought that it might be useful to be able to copy the std::cout stream to a file stream, thus logging any console messages output by my program to a text file. I came up with a solution to this with a custom stream buffer which could be used in conjunction with cout.rdbuf() - although I will save the precise details for perhaps another post. What I then needed, however, was a way to make sure that any changes I made to cout's stream buffer remained active for the duration of execution, but were returned to their original state (as well as making sure any open log files were cleanly closed) when the program exited. This was a prime candidate for self destruction using the above methods to make sure everything was tidied up when the execution stopped, even if by an unhandled exception.
class LogCout
{
public:
static void Begin(){static LogCout l;};
private:
LogCout(){//set up multiple stream output};
~LogCout(){//return cout to original settings and close any files streams};
};
If you're wondering why the constructor and destructor are private members, then I ought to point out that this particular class also implements a singleton. Modifying cout only needs to be done once, which is guaranteed by the static object l in the static function Begin(). This simplifies the use of the class even more because at the top of the main function I only need to call
LogCout::Begin();
and nothing more. Anything sent to cout is also logged to a file during execution, and everything is tidied up automatically when the program quits, however it quits, all thanks to self destruction.
template <class T>
class SmartPtr
{
public:
SmartPtr(T* rawPtr = 0) : m_ptr(rawPtr){};
~SmartPtr(){delete m_ptr;};
private:
T* m_ptr;
};
Ultimately you have a raw pointer wrapped in a class, the gist of it being this: whenever a SmartPtr object goes out of scope and the destructor is called it guarantees that the memory allocated from the heap is released via a call to delete. This is not only useful for preventing those times when you just plain forget to call delete when using raw pointers, it also handles cases like this:
MyClass* ptr = new MyClass();
ptr->DoStuff();
delete ptr;
This looks OK on the surface, but who knows where DoStuff() goes, or how long it'll take to return. At any point it could throw an exception and this is where we come across a problem: if the exception is unhandled and the program bails then delete will never be called causing a resource leak. On the other hand, using SmartPtr makes things more manageable:
SmartPtr<MyClass> ptr = SmartPtr<MyClass>(new MyClass());
ptr->DoStuff();
Immediately noticeable is the lack of a call to delete - this is because now that it is in the SmartPtr destructor it is guaranteed to be called, either when the SmartPtr goes out of scope or if an exception is thrown. In fact trying to call delete on a SmartPtr object would be syntactically incorrect.
Of course I'm only reiterating the fundamental use of smart pointers, this is information which can be found anywhere there is discussion of dynamic memory management in C++, and I mention this only to highlight one of the uses of the destructor.
To highlight another use the next example, from Effective C++, shows how the destructor comes into play when using a threading library. When accessing shared resources from multiple threads it is common practice to use a mutex to lock a resource while a thread accesses it. Once it has been accessed the mutex needs to be unlocked again to make the resource available to the other threads.
mutex.lock();
DoStuffToSharedResource();
mutex.unlock();
Already there are striking similarities to the smart pointer example, only this time we are calling lock() and unlock() rather than new and delete respectively, and the ramifications of failing to call unlock(), while not similar, could be considered just as dire as not calling delete on a pointer. It is only a short jump, then, to creating a Lock class which works a bit like a smart pointer.
class Lock
{
public:
Lock(Mutex& mutex) : m_mutex(mutex){mutex.lock();};
~Lock(){m_mutex.unlock();};
private:
Mutex& m_mutex;
};
Here the mutex is passed to the Lock class constructor which stores a reference to it and calls its lock() function. The destructor then takes care of making sure the mutex is unlocked when the lock object goes out of scope. Once again it also makes the example code shorter:
Lock lock(mutex);
DoStuffToSharedResource();
So useful is this, that it is exactly how the Lock class works in SFML (the threading classes in SFML are, in fact, very useful in general, for which I can vouch after having made use of them in sfchat).
So there you have two good examples of common practices which take advantage of class destructors. A final example is a practical use I deployed myself, since having learned the above methods. When working on a particular project I thought that it might be useful to be able to copy the std::cout stream to a file stream, thus logging any console messages output by my program to a text file. I came up with a solution to this with a custom stream buffer which could be used in conjunction with cout.rdbuf() - although I will save the precise details for perhaps another post. What I then needed, however, was a way to make sure that any changes I made to cout's stream buffer remained active for the duration of execution, but were returned to their original state (as well as making sure any open log files were cleanly closed) when the program exited. This was a prime candidate for self destruction using the above methods to make sure everything was tidied up when the execution stopped, even if by an unhandled exception.
class LogCout
{
public:
static void Begin(){static LogCout l;};
private:
LogCout(){//set up multiple stream output};
~LogCout(){//return cout to original settings and close any files streams};
};
If you're wondering why the constructor and destructor are private members, then I ought to point out that this particular class also implements a singleton. Modifying cout only needs to be done once, which is guaranteed by the static object l in the static function Begin(). This simplifies the use of the class even more because at the top of the main function I only need to call
LogCout::Begin();
and nothing more. Anything sent to cout is also logged to a file during execution, and everything is tidied up automatically when the program quits, however it quits, all thanks to self destruction.
Comments
Post a Comment