Other than in other languages like Java or Python where the assignment Cat cat1 = cat2 copies the reference / pointer of that object to a new variable, C++ invokes the copy constructor and instantiates a new instance. The Java behavior corresponds roughly to Cat* cat1 = &cat2.

How to use the copy constructor

#include <iostream>

class Cat {
public:
    int age;

    Cat() : age(5) {
        std::cout << "Default constructor called" << std::endl;
    }

    Cat(const Cat& cat) : age(cat.age){
        std::cout << "Copy constructor called" << std::endl;
    }

    Cat& operator=(const Cat& rhsCat) {
        std::cout << "Assignment operator called" << std::endl;
        return *this;
    }
};

void fun(Cat cat) {
    std::cout << "Inside fun" << std::endl;
};

int main() {
    //copy constructor
    std::cout << "Copy constructor:" << std::endl;
    Cat cat;
    Cat cat1 = Cat(cat); //explicit safe
    Cat cat2 = cat;
    fun(Cat(cat)); //explicit safe
    fun(cat);

    return 0;
}

Output:

Copy constructor:
Default constructor called
Copy constructor called
Copy constructor called
Copy constructor called
Inside fun
Copy constructor called
Inside fun

Related example: overloading of the assignment operator

Different ways to call the copy constructor

These are legitimate ways to use the copy constructor (probably still missing some expressions which look like cartoon character curses…):

Cat cat1 = Cat(cat);
Cat cat2 = cat;
Cat cat3(cat);
Cat cat4{cat};
Cat cat5 = {cat};
Cat cat6 = Cat{cat};

But only cat1, cat3, cat4 and cat6 would still work if the constructor were explicit (which is not recommended for the copy constructor; notes on explicit). If declaration and assignment are on the same line, then the copy constructor is called and not the assignment operator. The copy constructor is also called for functions with call by value arguments instead of call by reference arguments, fun(cat) in the example above.

Default copy constructor if none is implemented

If no copy constructor is defined, all member variables will be copied (C++ default copy constructor). This can be dangerous with pointers:

class Cat {
    int x;
    int* array = new int[5];
public:
    Cat() : x(1) {
        std::cout << "Default constructor called" << std::endl;
    }

    /*Cat(const Cat& cat) {
        std::cout << "Copy constructor called" << std::endl;
    }
     */

    int getX() { return x; }
    int* getArray() { return array; }
    void fillArray(int start) {
        for (int i = 0; i < 5; i++)
            array[i] = start + i;
    }

    void printArray() {
        for (int i = 0; i < 5; i++) {
            std::cout << array[i] << " ";
        }
        std::cout << std::endl;
    }
};

void fun(Cat party) {};

int main() {

    Cat cat;
    Cat cat1 = Cat(cat);
    cat1.fillArray(2);
    Cat cat2 = cat1;
    cat2.fillArray(5);
    std::cout << "cat1: ";
    cat1.printArray();
    std::cout << "cat2: ";
    cat2.printArray();
    std::cout << "cat1 x: " << cat1.getX() << std::endl;
    std::cout << "cat2 x: " << cat2.getX() << std::endl;
    fun(cat);

    return 0;
}

Output:

Default constructor called
cat1: 5 6 7 8 9 
cat2: 5 6 7 8 9 
cat1 x: 1
cat2 x: 1

cat2 overrides the array of cat1 because cat2’s array pointer points to the same address as cat1 since it is copied. This can also lead to crashes when the destructor is called twice and tries to free the same address twice via delete. To avoid this the copy constructor needs to be implemented.

If the code above contained:

~Cat() { delete[] array; }

Would lead to:

Default constructor called
cat1: 5 6 7 8 9 
cat2: 5 6 7 8 9 
cat1 x: 1
cat2 x: 1
copy_constructor(1866,0x7fffa4c81380) malloc: *** error for object 0x7fe8e5400360: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug

Categories: