Fundamentals 39 min read

When Does C++ Generate a Default Copy Constructor? Deep Dive and Pitfalls

This article explains what a copy constructor is, when the C++ compiler automatically generates a default copy constructor, and why relying on shallow copying can cause serious bugs, especially with inheritance, virtual functions, and resource‑managing members.

Deepin Linux
Deepin Linux
Deepin Linux
When Does C++ Generate a Default Copy Constructor? Deep Dive and Pitfalls

In C++ programming, the copy constructor plays a crucial role by initializing a new object of the same type using an existing one. The compiler does not always generate a default copy constructor; it does so only in specific situations, such as when a class contains a member of another class type that has its own copy constructor, or when a class inherits from a base class with a copy constructor.

1. What Is a Copy Constructor

A copy constructor is a special constructor whose parameter is a reference to an object of the same class, typically declared as ClassName(const ClassName& other). It is invoked when an object is initialized from another object of the same type.

class Student {
public:
    std::string name;
    int age;
    // Copy constructor
    Student(const Student& other) : name(other.name), age(other.age) {
        std::cout << "Copy constructor called" << std::endl;
    }
};

Using the copy constructor:

int main() {
    Student s1;
    s1.name = "Alice";
    s1.age = 20;
    Student s2 = s1; // Calls copy constructor
    return 0;
}

When the Copy Constructor Is Invoked

Object initialization : MyClass obj2 = obj1; Function parameter passing by value : the argument is copied into the parameter.

Function return by value : a temporary copy is created for the return value.

2. Situations That Trigger the Copy Constructor

The copy constructor is called in three common scenarios:

Object initialization.

Passing an object to a function by value.

Returning an object from a function by value.

2.1 Object Initialization

class Point {
public:
    int x;
    int y;
    Point(int a, int b) : x(a), y(b) {}
    Point(const Point& other) : x(other.x), y(other.y) {
        std::cout << "Copy constructor called during initialization" << std::endl;
    }
};
int main() {
    Point p1(1, 2);
    Point p2 = p1; // Calls copy constructor
    return 0;
}

2.2 Function Parameter Passing

void printRectangle(Rectangle rect) {
    std::cout << "Width: " << rect.width << ", Height: " << rect.height << std::endl;
}
int main() {
    Rectangle r1(10, 5);
    printRectangle(r1); // Calls copy constructor
    return 0;
}

2.3 Function Return Value

class Circle {
public:
    int radius;
    Circle(int r) : radius(r) {}
    Circle(const Circle& other) : radius(other.radius) {
        std::cout << "Copy constructor called on return" << std::endl;
    }
};
Circle createCircle() {
    Circle c(5);
    return c; // Calls copy constructor
}
int main() {
    Circle myCircle = createCircle();
    return 0;
}

3. Cases Where Bitwise Copy Is Insufficient

3.1 Member Objects Requiring Deep Copy

If a class contains a member like std::string that has its own copy constructor, a shallow bitwise copy would make both objects share the same internal buffer, leading to double‑free errors.

3.2 Inheritance from a Base Class with a Copy Constructor

class Base {
public:
    Base(int x) : val(x) {}
    Base(const Base& other) : val(other.val) {}
private:
    int val;
};
class Derived : public Base {
public:
    Derived(int x, int y) : Base(x), derivedVal(y) {}
private:
    int derivedVal;
};
int main() {
    Derived d1(10, 20);
    Derived d2 = d1; // Base copy constructor is invoked
    return 0;
}

3.3 Classes with Virtual Functions

When a class declares virtual functions, the compiler generates a copy constructor to correctly copy the virtual‑function table pointer, preserving polymorphic behavior.

class Shape {
public:
    virtual void draw() const = 0;
    int color;
    Shape(int c) : color(c) {}
};
class Circle : public Shape {
public:
    int radius;
    Circle(int c, int r) : Shape(c), radius(r) {}
    void draw() const override { std::cout << "Drawing a circle" << std::endl; }
};
int main() {
    Circle c1(255, 5);
    Circle c2 = c1; // Copies vtable pointer
    return 0;
}

3.4 Virtual Inheritance

Virtual inheritance introduces a virtual‑base‑class table (vbtable). The default copy constructor ensures the virtual base is copied only once and its pointer is correctly initialized.

class Base { public: int baseData; Base(int d) : baseData(d) {} };
class Derived1 : virtual public Base { public: Derived1(int d) : Base(d) {} };
class Derived2 : virtual public Base { public: Derived2(int d) : Base(d) {} };
class Final : public Derived1, public Derived2 { public: int finalData; Final(int b, int f) : Base(b), Derived1(b), Derived2(b), finalData(f) {} };
int main() {
    Final f1(10, 20);
    Final f2 = f1; // Correctly copies the single virtual Base subobject
    return 0;
}

4. When Does the Compiler Generate a Default Copy Constructor?

The compiler generates a default copy constructor only when needed, such as:

When the class has member objects of class type that have copy constructors.

When the class participates in inheritance and the base class has a copy constructor.

When the class contains virtual functions.

When the class is involved in virtual inheritance.

4.1 Member Objects

If a class contains members of another class that defines a copy constructor, the compiler synthesizes a default copy constructor that invokes the member’s copy constructor.

4.2 Inheritance

For a derived class, the generated copy constructor first calls the base class’s copy constructor, then copies its own members.

4.3 Virtual Functions

Classes with virtual functions need a copy constructor to copy the vtable pointer correctly.

4.4 Virtual Inheritance

In virtual inheritance, the default copy constructor ensures the shared virtual base is copied only once and its internal pointers are set properly.

5. Common Misconceptions About Copy Constructors

5.1 "Default copy constructor works for everything"

The default copy constructor performs a shallow copy. For classes that manage dynamic resources (e.g., raw pointers), this leads to double‑free and dangling‑pointer bugs.

5.2 "It is only called when I invoke it explicitly"

The compiler silently calls the copy constructor in many situations: object initialization, passing arguments by value, and returning objects by value.

5.3 "It is the same as the assignment operator"

The copy constructor creates a new object, while the assignment operator copies into an existing object. Their semantics differ.

6. Deep Dive Into the Default Copy Constructor

6.1 What It Does

If no user‑defined copy constructor exists, the compiler generates one that copies each non‑static data member. Built‑in types are copied by value; class‑type members have their own copy constructors invoked.

6.2 When It Is Generated

The compiler synthesizes it when the program requires copying and the class has not provided its own copy constructor.

6.3 How It Works Internally

For built‑in members, a byte‑wise copy is performed. For class‑type members, their copy constructors are called, ensuring proper construction of subobjects.

6.4 When to Provide a Custom Copy Constructor

If a class owns resources like dynamic memory, a custom deep‑copy constructor is needed to allocate separate storage for each object, preventing shared ownership problems.

class String {
public:
    char* str;
    int length;
    String(const char* s) {
        length = std::strlen(s);
        str = new char[length + 1];
        std::strcpy(str, s);
    }
    // Deep copy constructor
    String(const String& other) {
        length = other.length;
        str = new char[length + 1];
        std::strcpy(str, other.str);
    }
    ~String() { delete[] str; }
};

7. Interview Questions About C++ Copy Constructors

7.1 When does the compiler generate a default copy constructor?

When the class has no user‑defined copy constructor and a copy operation is required (e.g., object initialization, pass‑by‑value, return‑by‑value), especially if the class has members or base classes that need copying.

7.2 Does a class with only primitive members get a default copy constructor?

Technically the compiler can perform a trivial bitwise copy without emitting a separate constructor; the effect is the same as a default copy constructor.

7.3 What are the characteristics of the default copy constructor?

It copies built‑in members by value and invokes copy constructors of class‑type members, resulting in a shallow copy unless members themselves perform deep copying.

7.4 Problems with pointer members and the default copy constructor?

Shallow copying of pointers leads to shared ownership, double deletion, and dangling pointers.

7.5 Does defining a parameterized constructor suppress the default copy constructor?

No. A user‑defined parameterized constructor does not affect the generation of the default copy constructor.

7.6 How does a derived class’s default copy constructor handle the base part?

It first calls the base class’s copy constructor (default or user‑defined) to copy the base subobject.

7.7 How to disable the default copy constructor?

Declare it as ClassName(const ClassName&) = delete; (C++11) or make it private without definition.

7.8 Special handling for virtual inheritance?

The generated copy constructor ensures the virtual base subobject is copied only once and its internal offsets are correctly set.

7.9 Does the presence of a move constructor affect default copy constructor generation?

No. The move constructor handles rvalue copies; the copy constructor is still generated when a copy of an lvalue is required.

7.10 How to detect if the default copy constructor is called?

Insert diagnostic output in the constructor or set a breakpoint on the synthesized function; examining generated assembly can also reveal calls to the copy constructor.

C++deep copyshallow copyobject copyingCopy ConstructorDefault Constructor
Deepin Linux
Written by

Deepin Linux

Research areas: Windows & Linux platforms, C/C++ backend development, embedded systems and Linux kernel, etc.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.