Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

A copy constructor is a special member function in C++ that initializes a newly instantiated object by duplicating the state of an existing object of the exact same type. It dictates the memory allocation and value assignment semantics during object duplication.

Syntax

The standard signature of a copy constructor requires a reference to an object of the same class, typically const-qualified.
class StandardCopy {
public:
    // Standard copy constructor declaration
    StandardCopy(const StandardCopy& source);
};

class DeletedCopy {
public:
    // Explicitly deleted copy constructor
    DeletedCopy(const DeletedCopy& source) = delete;
};

Core Mechanics and Rules

1. Pass-by-Reference Requirement The parameter must be passed by reference (&). Declaring a copy constructor with a pass-by-value parameter is strictly forbidden by the C++ standard and results in a compile-time error. Conceptually, passing by value would require invoking the copy constructor to create the parameter itself, creating an infinite loop; the compiler prevents this by rejecting the syntax entirely. 2. The const Qualifier While not strictly required by the compiler, the const qualifier is standard practice. It guarantees the source object remains unmodified during the copy process and allows the constructor to bind to temporary objects (rvalues) and const instances. 3. Initialization vs. Assignment The copy constructor is invoked exclusively during the initialization of a new object. It is distinct from the copy assignment operator (operator=), which modifies an already existing object.
class MyClass {
public:
    // Explicitly defaulting the default constructor is required here 
    // because declaring any copy constructor suppresses its implicit generation.
    MyClass() = default;
    
    MyClass(const MyClass& source) = default;
    MyClass& operator=(const MyClass& source) = default;
};

MyClass obj1;
MyClass obj2 = obj1; // Invokes Copy Constructor
MyClass obj3(obj1);  // Invokes Copy Constructor

MyClass obj4;
obj4 = obj1;         // Invokes Copy Assignment Operator, NOT Copy Constructor

Compiler-Generated Default and Deleted Constructors

If no custom copy constructor is explicitly defined, the C++ compiler automatically generates a default inline copy constructor. The default implementation performs member-wise copy initialization. The copy semantics depend on the types of the member variables:
  • Primitive types and raw pointers are copied by value. For pointers, this copies the memory address, resulting in a shallow copy of the pointed-to object.
  • Class-type members (e.g., std::string, std::vector) are copied by invoking their respective copy constructors, which typically perform deep copies.
Implicitly Deleted Copy Constructors: If a class contains non-copyable members (e.g., std::unique_ptr, std::mutex, or members of a class where the copy constructor is explicitly deleted), the compiler will implicitly define the default copy constructor as deleted (= delete). Attempting to copy such an object will result in a compile-time error.
#include <string>
#include <memory>

class DefaultCopy {
    int id;             // Copied by value
    int* data;          // Copied by value (shallow copy)
    std::string name;   // Copied via std::string's copy constructor (deep copy)
    
    // Compiler generates:
    // DefaultCopy(const DefaultCopy& other) 
    //     : id(other.id), data(other.data), name(other.name) {}
};

class NonCopyable {
    std::unique_ptr<int> ptr; // Non-copyable member
    
    // Compiler implicitly generates:
    // NonCopyable(const NonCopyable& other) = delete;
};

Inheritance and Copy Constructors

When implementing a custom copy constructor in a derived class, developers must explicitly invoke the base class’s copy constructor in the member initializer list. If the base class copy constructor is omitted from the initializer list, the compiler will automatically invoke the base class’s default constructor. This results in a severe bug known as a “partial copy,” where the derived members are copied, but the base subobject is merely default-initialized.
class Base {
public:
    int base_val;
    Base() : base_val(0) {}
    Base(const Base& other) : base_val(other.base_val) {}
};

class Derived : public Base {
public:
    int derived_val;
    
    // CORRECT: Explicitly invoking the Base copy constructor
    Derived(const Derived& other) : Base(other), derived_val(other.derived_val) {}
    
    // INCORRECT: Base is default-initialized. base_val will be 0, not copied!
    // Derived(const Derived& other) : derived_val(other.derived_val) {} 
};

Custom Implementation, Resource Management, and Rule of Three

A custom copy constructor is mandatory when a class manages dynamically allocated memory to ensure a deep copy is performed. Without it, the default member-wise initialization duplicates raw pointer addresses, causing both objects to point to the same memory block, leading to double-free vulnerabilities upon destruction. Conversely, for classes managing unique raw system resources (e.g., file handles, network sockets, hardware locks), duplication is semantically invalid. In these cases, the copy constructor must be explicitly deleted (= delete), and the class should implement move semantics instead. When implementing a custom copy constructor for deep copying, developers must adhere to the Rule of Three. If a class requires a user-defined copy constructor, it requires a user-defined destructor and a user-defined copy assignment operator. In modern C++ (C++11 and later), explicitly defining a custom copy constructor prevents the compiler from implicitly generating a move constructor and a move assignment operator. Operations that could benefit from efficient move semantics will silently fall back to the copy constructor unless move operations are explicitly defined (the Rule of Five).
#include <cstring>
#include <new>

class DeepCopyClass {
private:
    char* buffer;
    std::size_t size;

public:
    DeepCopyClass(const char* str) {
        size = std::strlen(str);
        buffer = new char[size + 1];
        std::strcpy(buffer, str);
    }

    // 1. Custom Copy Constructor (Deep Copy)
    DeepCopyClass(const DeepCopyClass& source) : size(source.size) {
        buffer = new char[size + 1];
        std::strcpy(buffer, source.buffer);
    }

    // 2. Custom Copy Assignment Operator (Rule of Three)
    DeepCopyClass& operator=(const DeepCopyClass& source) {
        if (this != &source) {
            char* new_buffer = new char[source.size + 1];
            std::strcpy(new_buffer, source.buffer);
            
            delete[] buffer;
            buffer = new_buffer;
            size = source.size;
        }
        return *this;
    }

    // 3. Destructor (Rule of Three)
    ~DeepCopyClass() {
        delete[] buffer;
    }
};

class UniqueResource {
private:
    int socket_fd;
public:
    UniqueResource() = default;
    
    // Explicitly delete copy semantics for unique system resources
    UniqueResource(const UniqueResource&) = delete;
    UniqueResource& operator=(const UniqueResource&) = delete;
    
    // Move semantics would be implemented here
};

Invocation Triggers

At the compiler level, the copy constructor is triggered under four specific conditions:
  1. Direct or Copy Initialization: When an object is explicitly initialized using another object (T a(b); or T a = b;).
  2. Pass-by-Value: When an object is passed as an argument to a function by value, creating a local copy on the function’s stack frame.
  3. Return-by-Value: When a function returns an object by value. Note: Since C++17, copy elision for returning temporary objects (prvalues) is mandatory. It is no longer just an optimization; the copy constructor is completely bypassed and does not even need to be defined or accessible. For named local variables (NRVO), copy elision remains an optimization, though highly prevalent.
  4. Exception Handling: When an object is thrown as an exception by value (throw obj;) or caught by value (catch (MyClass e)).
Master C++ with Deep Grasping Methodology!Learn More