When writing efficient and bug-free code in C++, especially in embedded or systems programming, understanding deep copy vs shallow copy is critical. Misunderstanding these concepts can lead to memory corruption, crashes, or data inconsistencies β issues no firmware engineer wants in a production device.
This blog post will walk you through the fundamental difference between shallow copy and deep copy, and how they relate to pass-by-value and pass-by-reference in C++.

π¦ What is a Shallow Copy?
A shallow copy copies only the pointers or references, not the actual data they point to. That means both the original and the copied object share the same memory for dynamic or heap-allocated resources.
#include <cstring>
#include <iostream>
class Student
{
public:
char* name;
Student(const char* n)
{
name = new char[strlen(n) + 1];
strcpy(name, n);
}
// Default copy constructor (shallow copy)
~Student() { delete[] name; }
};
int
main()
{
Student s1("Amlendra");
Student s2 = s1; // shallow copy
std::cout << "s1 name: " << s1.name << "\n";
std::cout << "s2 name: " << s2.name << "\n";
return 0; // Crash due to double delete!
}
π Problem: s1 and s2 share the same memory for name. When both destructors try to delete the same memory, it causes a double free error.
π What is a Deep Copy?
A deep copy creates an independent copy of the entire object, including dynamically allocated memory. This ensures that the copy does not interfere with the original.
Example with Deep Copy Constructor:
#include <cstring>
#include <iostream>
class Student
{
public:
char* name;
Student(const char* n)
{
name = new char[strlen(n) + 1];
strcpy(name, n);
}
// Deep copy constructor
Student(const Student& other)
{
name = new char[strlen(other.name) + 1];
strcpy(name, other.name);
}
~Student() { delete[] name; }
};
int
main()
{
Student s1("Amlendra");
Student s2 = s1; // Deep copy
std::cout << "s1 name: " << s1.name << "\n";
std::cout << "s2 name: " << s2.name << "\n";
return 0; // Crash due to double delete!
}
β Note: Now each Student object manages its own memory. No double delete. No interference.
π§ Why Should Firmware Engineers Care?
In embedded systems:
- We work with limited memory.
- Manual memory management is common.
- Bugs due to shallow copy are hard to trace.
- You often pass structs or classes across drivers, RTOS tasks, or ISRs.
So, understanding when a deep copy is necessary (e.g., for buffers, configuration structs, or sensor data snapshots) is a must.
β οΈ One Caution:
In real embedded firmware:
- C++ exceptions are often disabled.
- Dynamic memory (new/delete) is either discouraged or tightly controlled.
π§Ύ Shallow Copy vs Deep Copy: Quick Summary:
| Aspect | Shallow Copy | Deep Copy |
|---|---|---|
| What it copies | Only references (pointers to data) | Entire object and all nested data |
| Memory shared? | Yes β both copies point to the same data | No β each copy has its own separate data |
| Safe to delete? | No β can cause double free or corruption | Yes β each copy is fully independent |
| Memory use | Low β reuses existing memory | Higher β allocates new memory |
| Best for | Simple, static, or read-only data | Complex, dynamic, or modifiable data |
βοΈ Shallow and Deep Copy in C++ Function Arguments:
Understanding how objects are passed to functions in C++ is important when using shallow and deep copies, especially if your class uses dynamic memory like pointers or arrays. C++ mainly supports two ways to pass objects to functions.
Pass By value:
When you pass an object by value to a function, C++ makes a copy of that object. This means the function works with the copied object, not the original one.
π οΈ How the Copy is Made:
- If you donβt write your own copy constructor, C++ will create one for you.
- This default copy just copies the objectβs data bit by bit β this is called a shallow copy.
π§ What That Means:
- If your class has simple variables like int, float, or std::string, this shallow copy usually works fine.
- But if your class uses pointers (like raw int*, char*, or arrays), both the original and the copy will point to the same memory.
- This can lead to problems, like:
- One object deleting memory the other still uses
- Unexpected changes in one object showing up in the other
- This can lead to problems, like:
π Safety Tip:
If your class uses raw pointers or dynamic memory:
1οΈβ£ You should write a custom copy constructor that performs a deep copyβthis means it allocates new memory and copies the actual data, not just the pointer.
To define a custom deep copy constructor, you need to:
- Allocate new memory for the copied object.
- Copy the actual data, rather than just copying the pointer.
// Example: Deep copy constructor
MyClass(const MyClass& other)
{
// Creates a true copy
data = new int(*other.data);
}
2οΈβ£ Or use smart pointers (std::unique_ptr, std::shared_ptr) to manage memory safely and automatically.
Pass By reference:
Passing by reference means the function receives a direct alias to the original object β no copy is made.
π Behavior:
- Fast and efficient β useful for large objects.
- Any modification inside the function affects the original.
π Safer with const:
- Use const reference to prevent modification:
void displaySensorValue(const MyClass& obj)
{
} // No copy, no mutation allowed
π‘οΈ Best Practices from a Firmware Engineer:
1. π Follow the Rule of Three (or Five): If your class owns raw pointers, implement:
- Copy constructor
- Copy assignment operator
- Destructor
(Modern C++: also move constructor and move assignment operator)
2.π§Ή Prefer smart pointers if allowed in your environment.
3. π§ͺ Always test object behavior in copy operations β simulate passing to functions, pushing into containers, etc.
4. π Avoid shallow copies unless memory is intentionally shared and well managed (e.g., for DMA buffers or static data).