Language
日本語
English

Caution

JavaScript is disabled in your browser.
This site uses JavaScript for features such as search.
For the best experience, please enable JavaScript before browsing this site.

  1. Home
  2. C++ Dictionary
  3. Copy Constructor / Assignment Operator

Copy Constructor / Assignment Operator

In C++, when a class holds resources such as pointers or dynamically allocated memory, the copy constructor and assignment operator generated by the compiler only perform a shallow copy. This leads to problems such as double-free or data corruption. Defining your own copy constructor and assignment operator to perform a deep copy is the fundamental rule of copy control.

Syntax

// ========================================
// Copy constructor and assignment operator basic syntax
// ========================================

class MyClass {
public:
    // Copy constructor (takes a const reference of the same type)
    MyClass(const MyClass& other) {
        // Deep copy the contents of other
    }

    // Copy assignment operator (self-assignment check required)
    MyClass& operator=(const MyClass& other) {
        if (this != &other) {  // Self-assignment check (handles a = a safely)
            // Release current resources
            // Deep copy the contents of other
        }
        return *this;  // Return reference to self to support chained assignment (a = b = c)
    }

    // Destructor (responsible for releasing resources)
    ~MyClass() {
        // Release dynamically allocated memory
    }
};

// ===================================================
// Rule of Three
// If any one of destructor, copy constructor, or copy
// assignment operator is defined, all three should be defined
// ===================================================

Syntax Reference

Syntax / ConceptDescription
Copy constructor
T(const T& other)
A constructor that takes an object of the same type as its argument and initializes itself. Implements a deep copy that duplicates the data pointed to by pointers, rather than a shallow copy.
Copy assignment operator
T& operator=(const T& other)
Called when one existing object is assigned to another. Performs a self-assignment check (if (this != &other)) and returns *this for chained assignment.
Rule of ThreeIf any of the destructor, copy constructor, or copy assignment operator is defined, all three must be defined. This is the fundamental rule for resource-managing classes.
Self-assignment check
if (this != &other)
Prevents the existing resource from being freed before copying when self-assignment (e.g., a = a) occurs.
Shallow copyThe copy generated by the compiler. Copies the pointer value (address) itself, causing multiple objects to point to the same memory and leading to double-free errors.
Deep copyA copy that independently duplicates the data pointed to by pointers. Implemented in a custom copy constructor and assignment operator.

Sample Code

deathnote_team.cpp
// ========================================
// deathnote_team.cpp
// Implements copy constructor and assignment operator
// for a class that holds DEATH NOTE investigation
// team member names in a dynamically allocated array
// ========================================

#include <iostream>
#include <string>
#include <cstring>  // Required for strcpy, strlen

class InvestigationTeam {
private:
    std::string teamName;
    char** members;
    int memberCount;

    void deepCopy(const InvestigationTeam& other) {
        teamName    = other.teamName;
        memberCount = other.memberCount;
        members = new char*[memberCount];
        for (int i = 0; i < memberCount; i++) {
            members[i] = new char[strlen(other.members[i]) + 1];
            strcpy(members[i], other.members[i]);
        }
    }

    void freeMemory() {
        for (int i = 0; i < memberCount; i++) {
            delete[] members[i];
        }
        delete[] members;
        members     = nullptr;
        memberCount = 0;
    }

public:
    InvestigationTeam(const std::string& name, const char* memberNames[], int count)
        : teamName(name), memberCount(count) {
        members = new char*[memberCount];
        for (int i = 0; i < memberCount; i++) {
            members[i] = new char[strlen(memberNames[i]) + 1];
            strcpy(members[i], memberNames[i]);
        }
        std::cout << "[Constructor]  " << teamName << " created." << std::endl;
    }

    // Copy constructor (Rule of Three 1/3)
    InvestigationTeam(const InvestigationTeam& other) {
        deepCopy(other);
        std::cout << "[Copy constructor]  " << teamName << " duplicated." << std::endl;
    }

    // Copy assignment operator (Rule of Three 2/3)
    InvestigationTeam& operator=(const InvestigationTeam& other) {
        if (this != &other) {
            freeMemory();
            deepCopy(other);
            std::cout << "[Assignment]  assigned to " << teamName << "." << std::endl;
        } else {
            std::cout << "[Assignment]  Self-assignment detected. No action." << std::endl;
        }
        return *this;
    }

    // Destructor (Rule of Three 3/3)
    ~InvestigationTeam() {
        std::cout << "[Destructor]  " << teamName << " destroyed." << std::endl;
        freeMemory();
    }

    void showMembers() const {
        std::cout << "[" << teamName << "]" << std::endl;
        for (int i = 0; i < memberCount; i++) {
            std::cout << "  " << (i + 1) << ". " << members[i] << std::endl;
        }
    }

    void addMember(const char* name) {
        char** newMembers = new char*[memberCount + 1];
        for (int i = 0; i < memberCount; i++) {
            newMembers[i] = members[i];
        }
        newMembers[memberCount] = new char[strlen(name) + 1];
        strcpy(newMembers[memberCount], name);
        delete[] members;
        members = newMembers;
        memberCount++;
        std::cout << "[addMember]  " << name << " added." << std::endl;
    }
};

int main() {
    std::cout << "--- 1. Create original ---" << std::endl;
    const char* kiraTeam[] = { "Yagami Light", "L" };
    InvestigationTeam team1("Kira Investigation Team", kiraTeam, 2);
    team1.showMembers();
    std::cout << std::endl;

    std::cout << "--- 2. Copy constructor ---" << std::endl;
    InvestigationTeam team1Copy(team1);
    team1Copy.showMembers();
    std::cout << std::endl;

    std::cout << "--- Add Misa to original ---" << std::endl;
    team1.addMember("Amane Misa");
    std::cout << "Original: ";
    team1.showMembers();
    std::cout << "Copy (unchanged): ";
    team1Copy.showMembers();
    std::cout << std::endl;

    std::cout << "--- 3. Copy assignment operator ---" << std::endl;
    const char* wammyMembers[] = { "Near", "Mello" };
    InvestigationTeam team2("Wammy's House Task Force", wammyMembers, 2);
    std::cout << std::endl;

    team2 = team1;
    std::cout << "After assignment, team2: ";
    team2.showMembers();
    std::cout << std::endl;

    std::cout << "--- 4. Self-assignment check ---" << std::endl;
    team1 = team1;
    std::cout << std::endl;

    std::cout << "--- Scope ends (destructors called) ---" << std::endl;
    return 0;
}
g++ -std=c++11 deathnote_team.cpp -o deathnote_team
./deathnote_team
--- 1. Create original ---
[Constructor]  Kira Investigation Team created.
[Kira Investigation Team]
  1. Yagami Light
  2. L

--- 2. Copy constructor ---
[Copy constructor]  Kira Investigation Team duplicated.
[Kira Investigation Team]
  1. Yagami Light
  2. L

--- Add Misa to original ---
[addMember]  Amane Misa added.
Original: [Kira Investigation Team]
  1. Yagami Light
  2. L
  3. Amane Misa
Copy (unchanged): [Kira Investigation Team]
  1. Yagami Light
  2. L

--- 3. Copy assignment operator ---
[Constructor]  Wammy's House Task Force created.

[Assignment]  assigned to Kira Investigation Team.
After assignment, team2: [Kira Investigation Team]
  1. Yagami Light
  2. L
  3. Amane Misa

--- 4. Self-assignment check ---
[Assignment]  Self-assignment detected. No action.

--- Scope ends (destructors called) ---
[Destructor]  Kira Investigation Team destroyed.
[Destructor]  Kira Investigation Team destroyed.
[Destructor]  Kira Investigation Team destroyed.

Common Mistake 1: Releasing resources without a self-assignment check

When self-assignment (a = a) occurs, freeing the resource first destroys the source data at the same time.

// NG: No self-assignment check before freeing
InvestigationTeam& operator=(const InvestigationTeam& other) {
    freeMemory();    // With a = a, own data is also erased
    deepCopy(other); // Reads already-freed data (undefined behavior)
    return *this;
}

OK: Check for self-assignment first with if (this != &other).

// OK: Check for self-assignment before proceeding
InvestigationTeam& operator=(const InvestigationTeam& other) {
    if (this != &other) {
        freeMemory();
        deepCopy(other);
    }
    return *this;
}
g++ -std=c++11 copy_assign_ok.cpp -o copy_assign_ok
./copy_assign_ok
[Assignment]  Self-assignment detected. No action.

Common Mistake 2: Defining only the destructor without following the Rule of Three

If only the destructor is defined while the copy constructor and assignment operator are left as compiler-generated (shallow copy), copying objects causes double-free errors.

// NG: Only destructor defined, copy operations omitted
class InvestigationTeam {
    char* name_;
public:
    InvestigationTeam(const char* n) {
        name_ = new char[strlen(n) + 1];
        strcpy(name_, n);
    }
    ~InvestigationTeam() { delete[] name_; }
    // No copy constructor or assignment operator defined
    // → compiler's shallow copy is used → double-free on name_
};

OK: When defining a destructor, also define the copy constructor and assignment operator (Rule of Three).

// OK: Define all three following the Rule of Three
class InvestigationTeam {
    char* name_;
public:
    InvestigationTeam(const char* n) { /* ... */ }
    InvestigationTeam(const InvestigationTeam& o) { /* deep copy */ }
    InvestigationTeam& operator=(const InvestigationTeam& o) { /* ... */ return *this; }
    ~InvestigationTeam() { delete[] name_; }
};
g++ -std=c++11 rule_of_three.cpp -o rule_of_three
./rule_of_three
After copy: Yagami Light
Original: Yagami Light (independent copy)

Overview

When designing a class that holds resources such as dynamic memory or file handles in C++, the "Rule of Three" principle requires that the destructor, copy constructor, and copy assignment operator be defined as a set. The compiler-generated copy only copies the pointer value (address) — a shallow copy — causing multiple objects to share the same memory and leading to double-free crashes whenever a destructor is called. The copy constructor and assignment operator must implement a deep copy, and the copy assignment operator must perform a self-assignment check with if (this != &other) before releasing existing resources. The copy assignment operator must also return *this by reference to support chained assignment (a = b = c). From C++11, move semantics were introduced, evolving this into the "Rule of Five", but mastering this Rule of Three is the essential foundation of copy control. For more details, see also Destructor and Operator Overloading.

If you find any errors or copyright issues, please .