C++: Copy Control

Last updated: 2026-08-26

In Lesson 29, we learned advanced constructors.

But if you want to copy objects, assign objects, or move objects, C++ has a set of copy control rules.

Understanding these rules is essential for writing correct classes.


1. Rule of Three/Five

(1) 1.1 What Is the Rule of Three/Five?

If your class needs a custom implementation of any of the following functions, then you must custom-implement all related functions:

Version Functions to Manage Description
Rule of Three (C++98) 1. Destructorbr2. Copy constructorbr3. Copy assignment operator Needed when there's dynamic memory
Rule of Five (C++11) Rule of Three +br4. Move constructorbr5. Move assignment operator C++11 introduced move semantics

(2) 1.2 Why the Rule of Three/Five?

If you need to customize one of them, it means the class has resources requiring special handling (like dynamic memory, file handles).

Example: Consequences of Not Following the Rule of Three (Difficulty ⭐⭐)

▶ Example 2: Code Example (Difficulty ⭐)

CPP
#include <iostream>
#include <cstring>

class BadString {
private:
 char* data;
 
public:
 // Constructor
 BadString(const char* s) {
 data = new char[strlen(s) + 1];
 strcpy(data, s);
 }
 
 // ❌ No custom copy constructor, copy assignment operator, or destructor
 // The compiler will generate defaults (shallow copy)
};

int main() {
 BadString s1("Hello");
 BadString s2 = s1; // ❌ Shallow copy: s1.data and s2.data point to the same memory
 
 // When main ends, s2 is destructed first (freeing data)
 // Then s1 is destructed (freeing data again, but it's already been freed) → Crash!
 
 return 0;
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
Hello
Hello

Consequence: Program crash (double free of the same memory).

Fix: Follow the Rule of Three — custom-implement all three functions.



2. Copy Assignment Operator*

(1) 2.1 Basic Usage

CPP
#include <iostream>
#include <cstring>

class MyString {
private:
 char* data;
 
public:
 // Constructor
 MyString(const char* s) {
 data = new char[strlen(s) + 1];
 strcpy(data, s);
 }
 
 // Copy constructor
 MyString(const MyString& other) {
 data = new char[strlen(other.data) + 1];
 strcpy(data, other.data);
 }
 
 // Copy assignment operator
 MyString& operator=(const MyString& other) {
 if (this != &other) { // Prevent self-assignment
 delete data; // Free old memory
 data = new char[strlen(other.data) + 1];
 strcpy(data, other.data);
 }
 return *this;
 }
 
 // Destructor
 ~MyString() {
 delete data;
 }
};

int main() {
 MyString s1("Hello");
 MyString s2("World");
 
 s2 = s1; // ✅ Calls the copy assignment operator
 
 return 0;
}

💡 Key Point: The copy assignment operator must check for self-assignment (if (this != &other)).



3. Move Semantics (C++11)*

(1) 3.1 Why Do We Need Move?

If you have a temporary object (about to be destroyed), copying its content is wasteful — it's better to just "steal" its resources.

Example: Using Move to Avoid Unnecessary Copies (Difficulty ⭐⭐)

TEXT 📖 Display only
#include <iostream>
#include <cstring>

class MyString {
private:
 char* data;
 
public:
 // Move constructor (C++11)
 MyString(MyString&& other) noexcept {
 data = other.data; // Steal the resource
 other.data = nullptr; // Set other to null
 }
 
 // Move assignment operator (C++11)
 MyString& operator=(MyString&& other) noexcept {
 if (this != &other) {
 delete data; // Free old resource
 data = other.data; // Steal the resource
 other.data = nullptr; // Set other to null
 }
 return *this;
 }
};

int main() {
 MyString s1("Hello");
 MyString s2 = std::move(s1); // ✅ Calls the move constructor
 // Now s1.data is nullptr, s2.data points to the original memory
 
 return 0;
}

💡 Tip: std::move() converts an lvalue to an rvalue reference, triggering the move constructor or move assignment operator.



4. = default and = delete*

(1) 4.1 Using = default to Let the Compiler Generate Default Implementations*

If your class doesn't need special copy logic, you can use = default to let the compiler generate the defaults.

CPP
#include <iostream>
#include <string>

class Student {
private:
 std::string name;
 int age;
 
public:
 // Let the compiler generate default copy constructor, copy assignment operator, and destructor
 Student(const Student& other) = default;
 Student& operator=(const Student& other) = default;
 ~Student() = default;
};

int main() {
 Student s1;
 Student s2 = s1; // ✅ Calls the default copy constructor
 
 return 0;
}

💡 Tip: If a class only has STL types (std::string, std::vector, etc.), using = default is sufficient.


(2) 4.2 Using = delete to Prohibit Copying*

If you don't want a class to be copied (e.g., singleton pattern), you can use = delete to prohibit it.

CPP
#include <iostream>

class Singleton {
private:
 Singleton() {} // Private constructor
 
public:
 // Prohibit copying
 Singleton(const Singleton&) = delete;
 Singleton& operator=(const Singleton&) = delete;
 
 static Singleton& getInstance() {
 static Singleton instance;
 return instance;
 }
};

int main() {
 Singleton& s = Singleton::getInstance();
 // Singleton s2 = s; // ❌ Compile error: copy constructor is deleted
 
 return 0;
}


5. Practice: Complete MyString Class*

▶ Example 1: Implementing a Complete MyString Class (Difficulty ⭐⭐⭐)

CPP 📖 Display only
#include <iostream>
#include <cstring>

class MyString {
private:
 char* data;
 int length;
 
public:
 // Constructor
 MyString(const char* s = "") {
 length = strlen(s);
 data = new char[length + 1];
 strcpy(data, s);
 }
 
 // Copy constructor
 MyString(const MyString& other) {
 length = other.length;
 data = new char[length + 1];
 strcpy(data, other.data);
 }
 
 // Move constructor
 MyString(MyString&& other) noexcept {
 data = other.data;
 length = other.length;
 other.data = nullptr;
 other.length = 0;
 }
 
 // Copy assignment operator
 MyString& operator=(const MyString& other) {
 if (this != &other) {
 delete data;
 length = other.length;
 data = new char[length + 1];
 strcpy(data, other.data);
 }
 return *this;
 }
 
 // Move assignment operator
 MyString& operator=(MyString&& other) noexcept {
 if (this != &other) {
 delete data;
 data = other.data;
 length = other.length;
 other.data = nullptr;
 other.length = 0;
 }
 return *this;
 }
 
 // Destructor
 ~MyString() {
 delete data;
 }
 
 // Other member functions
 void print() const {
 if (data) std::cout << data;
 }
 
 int size() const {
 return length;
 }
};

int main() {
 MyString s1("Hello");
 MyString s2 = s1; // Copy constructor
 
 s1.print(); // Hello
 std::cout << std::endl;
 
 s2.print(); // Hello
 std::cout << std::endl;
 
 return 0;
}
59 logic lines (exceeds 40-line limit, display only)

Output:

TEXT 📖 Display only
(program output)

▶ Example 3: Deep Copy Example (Difficulty ⭐)

CPP
#include <iostream>

class Buffer {
    int* data;
    int size;
public:
    Buffer(int s) : size(s), data(new int[s]) {}
    ~Buffer() { delete[] data; }

    // Deep copy constructor
    Buffer(const Buffer& other) : size(other.size), data(new int[other.size]) {
        for (int i = 0; i < size; i++) data[i] = other.data[i];
    }

    int& operator[](int i) { return data[i]; }
};

int main() {
    Buffer b1(3);
    b1[0] = 1; b1[1] = 2; b1[2] = 3;

    Buffer b2 = b1; // Deep copy
    b2[0] = 100;

    std::cout << "b1[0]=" << b1[0] << ", b2[0]=" << b2[0] << std::endl;

    return 0;
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
b1[0]=1, b2[0]=100
💡 Tip: Deep copy copies the content pointed to by the pointer, not just the pointer value. Modifying the copy does not affect the original object.


Q: When should I define a custom copy constructor? A: When the class has pointer members (pointing to dynamic memory).

If the class only has basic types (int, double) or STL types (std::string, std::vector), no custom copy constructor is needed — the compiler-generated one works correctly (STL types implement deep copy themselves).

Q: What do =default and =delete do? A: > - = default: lets the compiler generate the default implementation (member-by-member copy) > - = delete: prohibits a function (e.g., prohibiting copying)

Using = default is recommended over hand-writing copy logic (if no special logic is needed).

Q: What happens if I don't write a copy constructor? A: The compiler automatically generates a default copy constructor (member-by-member copy).

But if the class has pointer members, the default will perform a shallow copy (only copies the pointer value, not the content it points to), causing double free of memory.



❓ FAQ

Q When do I need to define a custom copy constructor?
A When a class manages dynamic memory or resources (e.g., pointer members), you must implement deep copy, otherwise the default shallow copy will cause double free.
Q What's the difference between a move constructor and a copy constructor?
A A copy constructor copies resources (deep copy), while a move constructor transfers resource ownership (shallow copy), which is more efficient.

📖 Summary


📝 Exercises

  1. Basic (Difficulty ⭐): Define a Point class with two members: x and y.

  2. Follow the Rule of Three — define a custom copy constructor, copy assignment operator, and destructor

  3. Test copying in main

  4. Intermediate (Difficulty ⭐⭐): Define a DynamicArray class (dynamic array) with:

  5. int* data (pointing to an array on the heap)

  6. int size (array size)

Follow the Rule of Five — implement all special member functions.

  1. Challenge (Difficulty ⭐⭐⭐): Extend the DynamicArray class by adding:
  2. void push_back(int x): add an element to the end of the array (if space is insufficient, double the capacity)
  3. void pop_back(): remove the last element

6. 🚀 Next Steps*

Now that you've learned copy control and move semantics, let's move on to Inheritance (Lesson 31) — creating "parent-child" relationships between classes for code reuse!

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏