A hidden danger of wrapping using RAII rears its head when we start copying the object around. Let's look at why and what we can do.
You may recall our RAII MIDI class from before:
class CMidiOut
{
public:
CMidiOut() :
m_hMidiOutHandle(nullptr)
{
midiOutOpen(
&m_hMidiOutHandle, MIDI_MAPPER, NULL, NULL, CALLBACK_NULL); }
~CMidiOut()
{
if(m_hMidiOutHandle != nullptr)
{
midiOutClose(m_hMidiOutHandle);
m_hMidiOutHandle = nullptr;
}
}
private:
HMIDIOUT m_hMidiOutHandle;
};
Clients of the class no longer have to worry about the raw API calls, nor do they have to remember to close the handle and can write exception happy code. A danger lies in the compiler generated copy constructor though, and also the assignment operator. These default implementations will take a copy of our handle, which is fine, but as soon as one of the copies (or the original) is destroyed, there will be a call to midiOutClose
. This is also okay, until one of the copies tries to use its handle. The handle has been closed. Our resource has been unacquired. Midi plays no more.
We would write defensive code to protect against this situation. We have two alternatives:
- Prevent copying of the RAII class.
- Use reference counting on duplicating the handle.
Of the two options, number 2 is by far the most interesting, but sadly we won't be using it here. DuplicateHandle does not work on HMIDIOUT
and a reference count solution would work but is far too complicated for what we need. Instead, we are going to simply turn the class into a pure wrapper for HMIDIOUT
and prevent any copies of it. This is done by making the copy constructor and assignment operator private
:
class CMidiOutHandle
{
public:
CMidiOutHandle();
~CMidiOutHandle();
private:
CMidiOutHandle(const CMidiOutHandle &);
CMidiOutHandle& operator=(const CMidiOutHandle&);
private:
HMIDIOUT m_hMidiOutHandle;
};
Note also the name change. We are now making it clear that this class can be used in place of a HMIDIOUT
(and it will, once we get to the accessor members).
Scott Meyer's in his excellent Effective C++ describes a reusable base class for copy prevention:
class Uncopyable
{
protected:
Uncopyable();
~Uncopyable();
private:
Uncopyable(const Uncopyable &);
Uncopyable& operator=(const Uncopyable&);
};
There is also a noncopyable class in Boost, which does pretty much the same thing. You can then simply inherit this from your noncopyable classes:
class CMidiOutHandle : private Uncopyable
I do like the self documentation here, but I don't actually like to use the base class method. If you try and copy the object using the first method, it will error and helpfully point you to the line which is doing the copying. If inheriting from the non copyable base class, the compiler will say the error is caused by your class. Not a big deal, but enough for me to explicitly disallow the copy constructor and assignment operator.