Search code examples
c++containscompositiondefault-copy-constructor

C++ implicit copy constructor for a class that contains other objects


I know that the compiler sometimes provides a default copy constructor if you don't implement yourself. I am confused about what exactly this constructor does. If I have a class that contains other objects, none of which have a declared copy constructor, what will the behavior be? For example, a class like this:

class Foo {
  Bar bar;
};

class Bar {
  int i;
  Baz baz;
};

class Baz {
  int j;
};

Now if I do this:

Foo f1;
Foo f2(f1);

What will the default copy constructor do? Will the compiler-generated copy constructor in Foo call the compiler-generated constructor in Bar to copy over bar, which will then call the compiler-generated copy constructor in Baz?


Solution

  • Foo f1;
    Foo f2(f1);
    

    Yes this will do what you expect it to:
    The f2 copy constructor Foo::Foo(Foo const&) is called.
    This copy constructs its base class and then each member (recursively)

    If you define a class like this:

    class X: public Y
    {
        private:
            int     m_a;
            char*   m_b;
            Z       m_c;
    };
    

    The following methods will be defined by your compiler.

    • Constructor (default) (2 versions)
    • Constructor (Copy)
    • Destructor (default)
    • Assignment operator

    Constructor: Default:

    There are actually two default constructors.
    One is used for zero-initialization while the other is used for value-initialization. The used depends on whether you use () during initialization or not.

    // Zero-Initialization compiler generated constructor
    X::X()
        :Y()                // Calls the base constructor
                            //     If this is compiler generated use 
                            //     the `Zero-Initialization version'
        ,m_a(0)             // Default construction of basic PODS zeros them
        ,m_b(0)             // 
        m_c()               // Calls the default constructor of Z
                            //     If this is compiler generated use 
                            //     the `Zero-Initialization version'
    {
    }
    
    // Value-Initialization compiler generated constructor
    X::X()
        :Y()                // Calls the base constructor
                            //     If this is compiler generated use 
                            //     the `Value-Initialization version'
        //,m_a()            // Default construction of basic PODS does nothing
        //,m_b()            // The values are un-initialized.
        m_c()               // Calls the default constructor of Z
                            //     If this is compiler generated use 
                            //     the `Value-Initialization version'
    {
    }
    

    Notes: If the base class or any members do not have a valid visible default constructor then the default constructor can not be generated. This is not an error unless your code tries to use the default constructor (then only a compile time error).

    Constructor (Copy)

    X::X(X const& copy)
        :Y(copy)            // Calls the base copy constructor
        ,m_a(copy.m_a)      // Calls each members copy constructor
        ,m_b(copy.m_b)
        ,m_c(copy.m_c)
    {}
    

    Notes: If the base class or any members do not have a valid visible copy constructor then the copy constructor can not be generated. This is not an error unless your code tries to use the copy constructor (then only a compile time error).

    Assignment Operator

    X& operator=(X const& copy)
    {
        Y::operator=(copy); // Calls the base assignment operator
        m_a = copy.m_a;     // Calls each members assignment operator
        m_b = copy.m_b;
        m_c = copy.m_c;
    
        return *this;
    }
    

    Notes: If the base class or any members do not have a valid viable assignment operator then the assignment operator can not be generated. This is not an error unless your code tries to use the assignment operator (then only a compile time error).

    Destructor

    X::~X()
    {
                            // First runs the destructor code
    }
        // This is psudo code.
        // But the equiv of this code happens in every destructor
        m_c.~Z();           // Calls the destructor for each member
        // m_b              // PODs and pointers destructors do nothing
        // m_a          
        ~Y();               // Call the base class destructor
    
    • If any constructor (including copy) is declared then the default constructor is not implemented by the compiler.
    • If the copy constructor is declared then the compiler will not generate one.
    • If the assignment operator is declared then the compiler will not generate one.
    • If a destructor is declared the compiler will not generate one.

    Looking at your code the following copy constructors are generated:

    Foo::Foo(Foo const& copy)
        :bar(copy.bar)
    {}
    
    Bar::Bar(Bar const& copy)
        :i(copy.i)
        ,baz(copy.baz)
    {}
    
    Baz::Baz(Baz const& copy)
        :j(copy.j)
    {}