views:

73

answers:

2

The MFC's root object CObject's copy constructor and assignment are disabled by default.

The standard C++ default class copy constructor does a member-by-member copy. The presence of the private CObject copy constructor guarantees a compiler error message if the copy constructor of your class is needed but not available. You must therefore provide a copy constructor if your class requires this capability.

  • In CObject's source code, there is a comment:

Disable the copy constructor and assignment by default so you will get compiler errors instead of unexpected behaviour if you pass objects by value or assign objects.

My question is, what is the problem with the default bit-by-bit copy constructor for this CObject class? In my opinion, it would be better to give us the default copy constructor, and we could provide one if necessary (deep copy)

+2  A: 

Consider the following:

class CMyHandle : public CObject
{
    HANDLE hWin32;
public:
    CMyHandle()
    {
        hWin32 = SomeFunctionThatMakesHandle();
    }
    ~CMyHandle()
    {
        CloseHandle(hWin32);
    }
};

Now, if you copy CMyHandle, the handle ends up getting closed twice, and after the either one of the CMyHandle instances is destroyed, the other instance becomes invalid.

Since a large number of MFC's classes manage handles, this makes sense to force the creator of the class to explicitly override to create copy constructors and copy assignment operators.

EDIT: For example:

int main()
{
    CMyHandle h1; //CMyHandle's constructor initializes the handle
    {
        CMyHandle h2(h1); //Memberwise copy into h2. In this case, it copies the
                          //handle
    } //h2 destroyed, closes handle
    //h1 is now invalid (because the underlying handle was closed)!
}
Billy ONeal
+2  A: 

The default copy constructor is member-by-member, not bitwise.

Most CObject-derived classes contain - and manage directly - some system resources, that have no reference counting or similar mechanism, so the choice was probably made with the default use case in mind.

e.g. CGDIObject is roughly:

class CGDIObject : public CObject
{
    HGDIOBJ m_handle;

    CGDIObject() : m_handle(0) {}
    // derived classes provide a create, attach etc.
   ~CGDIObject() { DeleteObject(m_handle); } 
}

The default copy constructor here would be dangerous (leading to double destruction), providing a "correct" copy constructor is surprisingly hard and expensive.

Another reason may be that most CObject-derived classes are intended to be mutated, and thus passed by reference. A missing copy constructor will catch unintended copies that mutate a copy rather than the object passed:

class CMyObject : public CObject
{
   public:
      AttachFoo(FooHandle foo) { ... }
      AddBar() { ... }
};

bool InitMySession(CMyObject & obj)
{
    obj.AttachFoo(CreateRawFoo());   
    obj.AddBar();
    obj.AddBar();
}

// ...
CMyObj mo;
InitMySession(mo);

Omitting the "&" gives you code that compiles well, but creates a temporary copy, modifies that, and then drops it, while mo remains unmodified.

Quite many API's follow that pattern, as MFC doesn't rely on exceptions for error handling (for historic reasons: not all targeted compilers did support them well, and MFC requires a lot of extra resource handling that becomes painful with exceptions).


I don't think these choices are good, e.g. derived classes should be allowed to use the default copy constructor if their members permit (and most members should permit).

The decision fits the "mindset" of MFC, though, and the requriements / restrictions of the time MFC was created.

peterchen
good point, the close relationship between MFC and those system resource may let them decide to do so. I didn't quite understand your second point, what do you mean when speaking of mutated?
lz_prgmr
added more detail
peterchen