Rule of the Big Three

The Rule.

When a class requires any one of copy constructor, destructor, or assignment operator then it will usually require all three of them.

Rationale.

When a class owns and manages another object then there are two ways of implementing that relationship.  The easiest is to embed a definition for the other object inside the class' private area

class owner
    {
    Ownee ownee ;
    } ;

This very simple approach guarantees that the objects will have synchronized lifetime.  The ownee will be created as the owner is created and destroyed as the owner is destroyed.  Ownership of the contained object belongs to the outer object.  Ownership is a one to one relationship.  If an owner object it is copied then its contained part is automatically copied according to the rules of ownee's copy constructor.

Frequently this kind of relationship is not sufficiently flexible and the class will store a pointer to the object it manages.

class owner
    {
    Ownee * ownee ;
    } ;

This raises more problems.  The containing class requires a constructor to create the object pointed at and initialize the pointer.  Resource management means it also requires a destructor to prevent a memory leak when the owning object is destroyed.

class owner
    {
    Ownee * ownee ;
public :
    owner() : ownee(new Ownee) {}
    ~owner() { delete ownee ; }
    } ;

There are other problems too.  When an owner object is copied then the default C rules for copying mean that the stored pointer value is copied.  The effect is that two owner objects both believe they own the same ownee object.  Each will attempt to delete it in the destructor.  Double delete of a piece of memory is always an error.

Similarly, when an owner object is assigned then the default behaviour for assignment will copy the stored pointer value.  The effect again is two owner objects fighting over the same ownee object.  Again each will attempt to delete it in the destructor creating yet another error.  In addition the default assignment rules will leak whatever object the original left hand side object pointed at.

Our choices here are to either; disable copying and assignment, or implement them properly.  To disable them is easy.  Put the function signatures in a private part of the class, do not provide an implementation.  If code using the class attempts to copy or assign; the compiler will complain because the functions are private and therefore inaccessible.  If code inside the class attempts to copy or assign; the linker will complain because we provide no implementation.  It is always wise to comment these lines.

class owner
    {
    Ownee * ownee ;
    // Disable copy and assign
    owner(const owner &) ;
    owner & operator=(const owner &) ;
public :
    owner() : ownee(new Ownee) {}
    ~owner() { delete ownee ; }
    } ;

To implement them properly we must move away from the default "shallow copy" behaviour and implement what it is usually known as a "deep copy".

class owner
    {
    Ownee * ownee ;
public :
    owner() : ownee(new Ownee) {}
    // Supply deep copy and assign
    owner(const owner &original)
        : ownee(new Ownee(*original.ownee)) {}

    owner & operator=(const owner & rhs)
        {
        // discard old content
        delete ownee ;
        // New owned object is a copy of the
        // rhs' owned object
        ownee = new Ownee(*rhs.ownee) ;
        return *this ;
        }

    ~owner() { delete ownee ; }
    } ;

We have now demonstrated the point of the rule of the big three.

When a class requires any one of copy constructor or, destructor, assignment operator then it will usually require all three of them.  Omitting them gives a class that is easy to misuse and dangerous when it is misused.  Class designers should always strive to make their classes 1) safe and 2) hard to misuse.

Whilst classes that store raw pointers are the most obvious example there are many other situations where the rule of 'the big three' holds true.

 

Copyright 2002-15 by Dynamisys Ltd