Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / VC10.0

No RAII to copy

3.33/5 (3 votes)
20 Jun 2010CPOL2 min read 13.5K  
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.

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:

C++
class CMidiOut
{
public:
    CMidiOut()  :
        m_hMidiOutHandle(nullptr)
    {
        midiOutOpen(
            &m_hMidiOutHandle, 	// Handle
            MIDI_MAPPER,    	// Default device
            NULL,      		// No callback
            NULL,      		// No callback parameters
            CALLBACK_NULL);   	// Flags
    }
    ~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:

  1. Prevent copying of the RAII class.
  2. 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:

C++
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:

C++
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:

C++
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)