Copy Constructor
Master copy constructors to control how objects are copied and prevent shallow copy problems
Learn how to control object copying behavior and prevent dangerous shallow copy bugs when working with dynamically allocated resources.
A Simple Example
#include <iostream>
#include <cstring>
class String {
private:
char* data;
int length;
public:
String(const char* str = "") {
length = std::strlen(str);
data = new char[length + 1];
std::strcpy(data, str);
std::cout << "Constructor: " << data << "\n";
}
String(const String& other) {
length = other.length;
data = new char[length + 1];
std::strcpy(data, other.data);
std::cout << "Copy constructor: " << data << "\n";
}
~String() {
std::cout << "Destructor: " << data << "\n";
delete[] data;
}
void display() const {
std::cout << data << " (length: " << length << ")" << "\n";
}
void modify(char c) {
if (length > 0) {
data[0] = c;
}
}
};
void demonstrateCopy(String s) {
std::cout << "Inside function: ";
s.display();
}
int main() {
String s1{"Hello"};
String s2 = s1;
s1.display();
s2.display();
s1.modify('J');
std::cout << "After modifying s1:" << "\n";
s1.display();
s2.display();
demonstrateCopy(s1);
return 0;
}
Breaking It Down
Copy Constructor Signature
-
What it looks like:
ClassName(const ClassName& other) - Why const: Promises not to modify the source object being copied
- Why reference: Avoids infinite recursion (copying to make a copy would need a copy...)
- Remember: This signature is crucial - without the reference, it won't compile
Shallow vs Deep Copy
- Shallow copy: Default behavior, copies pointer values (addresses) not the data
- The problem: Multiple objects pointing to same memory location
- Deep copy: Allocates new memory and copies the actual data
- Remember: If your class has a pointer member, you almost certainly need a deep copy
When Copy Constructor is Called
-
Pass by value:
void func(String s)- copies parameter -
Return by value:
String getStr() { return str; }- copies return value -
Initialization:
String s2 = s1;orString s2{s1}; - Remember: These happen automatically, which is why proper copy construction matters
The Rule of Three
- If you need one, you need all three: Destructor, Copy Constructor, Assignment Operator
- Why: Managing resources requires control over copying and destruction
- Pattern: Allocate in constructor, deallocate in destructor, copy properly in copy constructor
- Remember: Classes with raw pointers or resource handles almost always follow this rule
Why This Matters
- When you pass objects by value or initialize one object from another, C++ copies them. The default copy just copies member values bit-by-bit (shallow copy), which causes disasters with dynamically allocated memory - both objects point to the same data!
- Copy constructors let you define deep copying logic, which is essential for any class managing resources like memory, files, or network connections.
- Understanding copy construction is the first step toward mastering C++'s resource management model and the Rule of Three.
Critical Insight
Without a copy constructor, copying objects with pointers creates a shallow copy - multiple objects pointing to the same memory. When one is destroyed, it deletes the memory, leaving others with dangling pointers that crash your program!
This is the famous "Rule of Three" - if you need a destructor to clean up resources, you likely need a copy constructor and assignment operator too. They work together to ensure your objects manage their resources safely.
Best Practices
Follow the Rule of Three: If you define a destructor, copy constructor, or copy assignment operator, define all three. They work together for proper resource management.
Deep copy when needed: Classes with pointer members or resource handles need deep copies that allocate new memory and copy the data.
Use const reference parameter: Always take const ClassName& as the parameter to prevent infinite recursion and unwanted modifications.
Consider Rule of Zero: When possible, use smart pointers (unique_ptr, shared_ptr) to avoid writing custom copy logic entirely.
Common Mistakes
Forgetting deep copy: Using default copy with dynamic memory causes double deletion. Both objects share the same pointer, and when one destructs, the other has a dangling pointer.
Wrong parameter type: Using ClassName other instead of const ClassName& other causes infinite recursion - copying the parameter requires calling the copy constructor.
Copying non-copyable resources: Some resources like file handles or mutexes should not be copied. Consider deleting the copy constructor for such classes.
Forgetting to copy all members: Make sure your copy constructor copies every relevant member variable, not just the pointer.
Debug Challenge
This copy constructor performs a shallow copy, causing a double-delete crash. Click the highlighted line to fix it:
Quick Quiz
- When is a copy constructor called?
- What is the problem with shallow copy?
- What is the correct copy constructor signature?
Practice Playground
Time to try out what you just learned! Play with the example code below, experiment by making changes and running the code to deepen your understanding.
Output:
Error:
Lesson Progress
- Fix This Code
- Quick Quiz
- Practice Playground - run once