Question
The Rule of Three in C++: Copying Objects, Copy Constructor, and Copy Assignment
Question
In C++, what does it mean to copy an object?
What are the copy constructor and the copy assignment operator, and how are they different?
When do I need to declare or define these special member functions myself?
Also, how can I prevent objects of a class from being copied at all?
Short Answer
By the end of this page, you will understand what object copying means in C++, how the copy constructor and copy assignment operator work, why the Rule of Three exists, when you must define these functions yourself, and how to disable copying for classes that should not be duplicated.
Concept
In C++, copying an object means creating or updating one object so it has the same state as another object.
There are two main copy operations:
- Copy constructor: creates a new object from an existing object
- Copy assignment operator: replaces the contents of an existing object with another object's contents
For example:
MyClass a;
MyClass b = a; // copy constructor
MyClass c;
c = a; // copy assignment operator
At first, this may seem simple. But copying becomes important when a class manages a resource such as:
- dynamically allocated memory
- file handles
- sockets
- mutexes
- database connections
If your class only contains simple values like int, double, or std::string, the compiler-generated copy behavior is usually correct.
But if your class owns a raw resource, a default copy can be dangerous. The compiler may perform a member-wise copy, which means it copies each field as-is. If one of those fields is a raw pointer, both objects may end up pointing to the same memory. That can cause:
- double deletion
- memory corruption
- unexpected shared state
This is where the Rule of Three comes from:
If a class needs a custom destructor, it usually also needs a custom copy constructor and copy assignment operator.
Why? Because if you manually manage a resource, you must define how that resource is:
Mental Model
Think of an object as a person carrying a backpack.
- A normal value field like
int ageis like writing the age on a card. Copying it is easy. - A raw pointer is like carrying a note with a storage locker number. Copying the note does not copy the locker contents.
Now imagine two people both hold a note to the same locker. If both think they own it and both try to empty and destroy it, problems happen.
That is what shallow copying does with raw pointers.
- Copy constructor = building a brand-new person with the same belongings
- Copy assignment = taking an existing person and replacing their belongings with someone else's
- Destructor = cleaning up what the person owns when they leave
The Rule of Three means: if your object owns a locker key, you must carefully define how the locker is copied, reassigned, and cleaned up.
Syntax and Examples
Core syntax
Copy constructor
ClassName(const ClassName& other);
Copy assignment operator
ClassName& operator=(const ClassName& other);
Prevent copying
ClassName(const ClassName&) = delete;
ClassName& operator=(const ClassName&) = delete;
Example: safe deep copy
#include <iostream>
#include <cstddef>
class Buffer {
private:
int* data;
std::size_t size;
public:
Buffer(std::size_t s) : data(new int[s]), size(s) {
for (std::size_t i = ; i < size; ++i) {
data[i] = ;
}
}
( Buffer& other) : ( [other.size]), (other.size) {
(std:: i = ; i < size; ++i) {
data[i] = other.data[i];
}
}
Buffer& =( Buffer& other) {
( != &other) {
* newData = [other.size];
(std:: i = ; i < other.size; ++i) {
newData[i] = other.data[i];
}
[] data;
data = newData;
size = other.size;
}
*;
}
~() {
[] data;
}
{
(index < size) {
data[index] = value;
}
}
{
(std:: i = ; i < size; ++i) {
std::cout << data[i] << ;
}
std::cout << ;
}
};
{
;
a.(, );
a.(, );
a.(, );
Buffer b = a;
;
c = a;
b.();
c.();
}
Step by Step Execution
Consider this example:
Buffer a(2);
a.set(0, 5);
a.set(1, 9);
Buffer b = a;
Here is what happens step by step.
1. Buffer a(2);
- The constructor runs.
- Memory for 2 integers is allocated with
new int[2]. a.datapoints to that memory.a.sizebecomes2.
2. a.set(0, 5);
- The first element of
a's array becomes5.
3. a.set(1, 9);
- The second element of
a's array becomes9.
At this point, a owns its own array:
Real World Use Cases
The Rule of Three matters when a class directly owns a resource.
Common examples
Managing dynamic memory
class ImageBuffer {
unsigned char* pixels;
int width;
int height;
};
If pixels is allocated manually, copying must be handled carefully.
Wrapping file handles
A class may open a file in its constructor and close it in its destructor. Copying such an object may not make sense unless ownership rules are clearly defined.
Socket or network resource wrappers
Two objects should usually not both believe they uniquely own the same socket.
Custom container classes
If you build your own vector-like or string-like class using raw pointers, copying must create separate storage.
Device or system resource managers
Some resources cannot be duplicated safely at all, so copying should be disabled.
When this matters less
If your class is made of safe standard library types, copying is usually already handled correctly.
class Person {
private:
std::string name;
int age;
};
Here, std::string already manages its own memory correctly, so you usually do not need custom copy code.
Real Codebase Usage
In real C++ codebases, developers usually try to avoid manual memory management where possible.
Common patterns
Prefer RAII types
Instead of owning raw pointers directly, use:
std::stringstd::vectorstd::arraystd::unique_ptrstd::shared_ptrwhen shared ownership is truly needed
These types already define correct cleanup and copying behavior.
Use = default when the compiler-generated version is correct
class Person {
public:
Person() = default;
Person(const Person&) = default;
Person& operator=(const Person&) = default;
~Person() = default;
};
This makes your intent explicit.
Use = delete to disable copying
Common Mistakes
1. Confusing initialization with assignment
These are different:
MyClass b = a; // copy constructor
b = a; // copy assignment operator
Beginners often think both lines do the same thing. They do not.
2. Forgetting the destructor when owning raw memory
Broken example:
class BadBuffer {
private:
int* data;
public:
BadBuffer() {
data = new int[10];
}
};
This leaks memory because nothing frees data.
3. Defining a destructor but not copy operations
Broken example:
class BadBuffer {
private:
int* data;
public:
BadBuffer() {
data = new int[10];
}
~BadBuffer() {
delete[] data;
}
};
This looks better, but copying is now dangerous because the default copy operations still shallow-copy the pointer.
Comparisons
| Concept | When it happens | What it does | Typical syntax |
|---|---|---|---|
| Copy constructor | When creating a new object from another object | Builds a new object as a copy | MyClass b = a; |
| Copy assignment operator | When assigning to an existing object | Replaces existing state | b = a; |
| Destructor | When an object is destroyed | Releases owned resources | automatic at scope end |
Shallow copy vs deep copy
| Type of copy | What gets copied | Safe for raw owning pointers? | Result |
|---|---|---|---|
| Shallow copy |
Cheat Sheet
Quick reference
Copy constructor
ClassName(const ClassName& other);
Used when creating a new object from an existing one.
ClassName b = a;
ClassName b(a);
Copy assignment operator
ClassName& operator=(const ClassName& other);
Used when assigning to an already existing object.
b = a;
Rule of Three
If your class defines one of these, it probably needs all three:
- destructor
- copy constructor
- copy assignment operator
Prevent copying
ClassName(const ClassName&) = delete;
ClassName& operator=(const ClassName&) = delete;
Safe assignment checklist
- check for self-assignment:
if (this != &other)
FAQ
What is the Rule of Three in C++?
It says that if a class needs a custom destructor, it usually also needs a custom copy constructor and copy assignment operator.
What does copying an object mean in C++?
It means making one object take the same state as another, either by constructing a new object from it or assigning to an existing one.
What is the difference between copy constructor and copy assignment?
The copy constructor creates a new object from another object. The copy assignment operator copies into an already existing object.
When do I need to write a copy constructor myself?
Usually when your class directly owns a resource such as dynamically allocated memory or another unique resource.
How do I stop a class from being copied?
Delete the copy constructor and copy assignment operator:
MyClass(const MyClass&) = delete;
MyClass& operator=(const MyClass&) = delete;
Is the Rule of Three still relevant in modern C++?
Yes, but modern C++ often avoids manual resource management by using standard library types. You may also hear the Rule of Five.
Does std::string require the Rule of Three?
No. std::string already manages its own memory correctly, so classes containing it usually do not need custom copy operations.
What is a shallow copy in C++?
A shallow copy copies member values directly. For raw pointers, that means copying the address rather than duplicating the pointed-to data.
Mini Project
Description
Build a small C++ class that manages a dynamically allocated character buffer. This project demonstrates why the Rule of Three exists by implementing safe copying for a class that owns raw memory.
Goal
Create a class that owns a heap-allocated string buffer, supports deep copying, and avoids double deletion.
Requirements
- Create a class that stores text in dynamically allocated memory.
- Implement a destructor to release the memory.
- Implement a copy constructor that performs a deep copy.
- Implement a copy assignment operator that safely copies data.
- Show in
main()that modifying one copy does not change another copy.
Keep learning
Related questions
Basic Rules and Idioms for Operator Overloading in C++
Learn the core rules, syntax, and common idioms for operator overloading in C++, including member vs non-member operators.
C++ Casts Explained: C-Style Cast vs static_cast vs dynamic_cast
Learn the difference between C-style casts, static_cast, and dynamic_cast in C++ with clear examples, safety rules, and real usage tips.
C++ Lambda Expressions Explained: What They Are and When to Use Them
Learn what C++ lambda expressions are, why they exist, when to use them, and how they simplify callbacks, algorithms, and local logic.