Copy Constructor
Part of the Object Oriented Programming section of Coddy's C++ journey — lesson 20 of 104.
A copy constructor creates a new object as a copy of an existing object. It's called when you initialize an object from another object of the same type, pass an object by value, or return an object by value.
The copy constructor takes a const reference to an object of the same class:
class Player {
std::string name;
int health;
public:
Player(std::string n, int h) : name(n), health(h) {}
// Copy constructor
Player(const Player& other) {
name = other.name;
health = other.health;
}
};
Player original("Hero", 100);
Player copy = original; // Copy constructor called
Player another(original); // Also calls copy constructorIf you don't define a copy constructor, the compiler generates one that performs a shallow copy - it copies each member's value directly. This works fine for simple types, but causes problems when your class manages dynamic memory.
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : size(s) {
data = new int[size];
}
// Deep copy constructor
Buffer(const Buffer& other) : size(other.size) {
data = new int[size]; // Allocate new memory
for (size_t i = 0; i < size; i++)
data[i] = other.data[i]; // Copy contents
}
~Buffer() { delete[] data; }
};Without a custom copy constructor, both objects would point to the same memory. When one is destroyed, the other would have a dangling pointer. A deep copy allocates new memory and copies the actual data, ensuring each object owns its own resources.
Challenge
EasyLet's build a message history system that demonstrates the importance of deep copying when your class manages dynamic memory. You'll create a MessageLog class that stores messages in a dynamically allocated array, and implement a proper copy constructor to ensure each copy has its own independent memory.
You'll create two files to organize your code:
MessageLog.h: Define aMessageLogclass that manages a collection of messages using dynamic memory. Your class should have:- Private members: a pointer to a string array for storing messages, a
capacityfor the maximum number of messages, and acountfor the current number of messages - A parameterized constructor that takes a capacity, allocates the array on the heap, and initializes count to 0
- A copy constructor that performs a deep copy — allocating new memory and copying all messages from the source object
- A destructor that frees the allocated memory and prints
"MessageLog destroyed" - An
addMessage(string msg)method that adds a message if there's room - A
getCount()method that returns the current message count - A
getMessage(int index)method that returns the message at the given index
- Private members: a pointer to a string array for storing messages, a
main.cpp: Demonstrate that your copy constructor creates an independent copy. Read a capacity value and two messages from input, then:- Create a
MessageLogcalledoriginalwith the input capacity - Add both messages to
original - Create a copy called
backupusing the copy constructor - Add a third message
"New message"tooriginalonly - Print
"Original count: <count>" - Print
"Backup count: <count>" - Print all messages from
backup, one per line
- Create a
The input format will be:
- First line: capacity (integer)
- Second line: first message (string)
- Third line: second message (string)
If your deep copy is implemented correctly, the backup should have 2 messages while the original has 3 — proving they have independent memory. Without a proper copy constructor, both would share the same array and changes to one would affect the other!
Include your header file in main.cpp using #include "MessageLog.h".
Cheat sheet
A copy constructor creates a new object as a copy of an existing object. It takes a const reference to an object of the same class:
class Player {
std::string name;
int health;
public:
Player(std::string n, int h) : name(n), health(h) {}
// Copy constructor
Player(const Player& other) {
name = other.name;
health = other.health;
}
};
Player original("Hero", 100);
Player copy = original; // Copy constructor called
Player another(original); // Also calls copy constructorIf you don't define a copy constructor, the compiler generates one that performs a shallow copy - copying each member's value directly. This causes problems when your class manages dynamic memory.
A deep copy constructor allocates new memory and copies the actual data, ensuring each object owns its own resources:
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : size(s) {
data = new int[size];
}
// Deep copy constructor
Buffer(const Buffer& other) : size(other.size) {
data = new int[size]; // Allocate new memory
for (size_t i = 0; i < size; i++)
data[i] = other.data[i]; // Copy contents
}
~Buffer() { delete[] data; }
};Without a custom copy constructor, both objects would point to the same memory, causing dangling pointers when one is destroyed.
Try it yourself
#include <iostream>
#include <string>
#include "MessageLog.h"
using namespace std;
int main() {
// Read input
int capacity;
cin >> capacity;
cin.ignore();
string msg1, msg2;
getline(cin, msg1);
getline(cin, msg2);
// TODO: Create a MessageLog called 'original' with the input capacity
// TODO: Add both messages to original
// TODO: Create a copy called 'backup' using the copy constructor
// TODO: Add "New message" to original only
// TODO: Print "Original count: <count>"
// TODO: Print "Backup count: <count>"
// TODO: Print all messages from backup, one per line
return 0;
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesC++ Build & CompilationHeader Files & Source FilesNamespaces & ScopeIntroduction to OOP in C++Classes vs ObjectsThe 'this' PointerMethods (Member Functions)Attributes (Data Members)Ctors & Dtors BasicsRecap - Simple Calculator4Class Properties
Instance vs Static MembersGetters and SettersConst Member FunctionsMutable KeywordStatic Methods and VariablesFriend Functions & ClassesRecap - Bank Account Manager7Inheritance
Basic InheritanceInheritance Access LevelsCtor & Dtor Call OrderMethod OverridingVirtual Functions & VTableMultiple InheritanceVirtual InheritanceRecap - Employee Hierarchy2Memory Management
Stack vs Heap MemoryPointers and ReferencesDynamic Memory (new/delete)Smart Pointers in C++RAII in C++Recap - Dynamic Array Manager5Encapsulation
Access Specifiers in C++Access Specifiers In DepthInformation HidingStruct vs ClassNested & Inner ClassesRecap - Student Records System8Polymorphism
Compile vs Runtime PolymorphFunction OverloadingVirtual Functions RevisitedPure Virtual FunctionsAbstract ClassesInterface Design in C++Dynamic Casting & RTTIRecap - Shape Calculator3Constructors & Destructors
Default ConstructorParameterized ConstructorCopy ConstructorMove ConstructorConstructor Init ListsDelegating ConstructorsDestructor Deep DiveRule of Three / Five / ZeroRecap - String Class6Operator Overloading
Intro to Operator OverloadArithmetic Operator OverloadComparison Operator OverloadStream OperatorsAssignment Operator Overload[] and () Operator OverloadType Conversion OperatorsRecap - Matrix Class9Templates
Function TemplatesClass TemplatesTemplate SpecializationVariadic TemplatesSFINAE & Type Traits BasicsRecap - Generic Container