Welcome to the first pikoTutorial!
Nowadays there are many high level languages in which you don’t have to care about how objects are copied around. However, if you want to write in C++, you must understand that copying is a very distinct operation which varies depending on the object that you deal with. In theory, C++ also gets the job done for you because you can have completely empty class (no copy constructor defined) and you will still be able to copy such object:
class SomeClass {};
int main()
{
SomeClass a; // construction
SomeClass b(a); // copy construction
}Note for beginners: if your class does not own any resource, consists only of trivially constructable types and does not encapsulate any logic, not implementing any custom constructors or desctructor is actually a good practice. This is called the rule of zero.
If so, then why would you ever bother writing your custom implementation of the copy constructor at all? Well, things start to get more complicated when your object are getting more complicated too.
Classes holding pointers
Imagine that your class allocates some memory and holds a pointer to that memory:
#include <iostream>
#include <memory>
class SomeClass
{
public:
SomeClass() : ptr_{std::make_shared<int>(0)} {}
void SetValue(const int value) { *ptr_ = value; }
int GetValue() const { return *ptr_; }
private:
std::shared_ptr<int> ptr_;
};
int main()
{
SomeClass a; // construction
SomeClass b(a); // copy construction
a.SetValue(24);
b.SetValue(36);
std::cout << a.GetValue() << std::endl; // one might expect 24, but the actual value is 36
std::cout << b.GetValue() << std::endl; // expected value: 36
}The output of such code is:
36
36Oops! It looks like with a call b.SetValue(36) we have overwritten also the value in object a. This is because the memory for ptr_ has been allocated only once – in the constructor of SomeClass. It means, that ptr_ in copy b still points to the same memory and by this, can change whatever resides in that memory.
Note for beginners: the thing that happened here is called a shallow copy.
To make a deep copy of the object, you must implement your custom copy constructor where you perform additional memory allocation dedicated for the copied object:
#include <iostream>
#include <memory>
class SomeClass
{
public:
SomeClass() : ptr_{std::make_shared<int>(0)} {}
SomeClass(const SomeClass& other) : ptr_{std::make_shared<int>(*other.ptr_)} {}
void SetValue(const int value) { *ptr_ = value; }
int GetValue() const { return *ptr_; }
private:
std::shared_ptr<int> ptr_;
};
int main()
{
SomeClass a; // construction
SomeClass b(a); // copy construction
a.SetValue(24);
b.SetValue(36);
std::cout << a.GetValue() << std::endl; // expected value: 24
std::cout << b.GetValue() << std::endl; // expected value: 36
}Now the output is:
24
36Note for beginners: when you create a class which manages certain resource (e.g. a pointer) and you need to implement a custom copy constructor, you most probably need to implement a move constructor as well and the assignment operators. This is called the rule of five.
Note for advanced: having
std::make_sharedonly in a constructor allows you to control the dynamic memory allocation (e.g. by constructing all the objects at the startup of your application). However, putting it in the copy constructor introduces potentially unlimitted number of dynamic memory allocations during the application runtime. It leads to heap fragmentation which then may cause a heap/stack collision. This may be important in embedded systems with poor resources.

AI is powerful. Snippets are instant.
Stop prompting for the same patterns repeatedly. Get almost 100 free VS Code snippets for C++, Python, CMake and Bazel from piko::snippets GitHub repository.
Classes holding non-copyable types
Another example may be a class which has a member variable whose copy constructor has been deleted:
#include <mutex>
class SomeClass
{
private:
std::mutex mtx_;
};
int main()
{
SomeClass a; // construction
SomeClass b(a); // copy construction
}When we try to run such code, we will immediately get a compilation error:
main.cpp: In function ‘int main()’:
main.cpp:12:18: error: use of deleted function ‘SomeClass::SomeClass(const SomeClass&)’
12 | SomeClass b(a); // copy construction
| ^What’s going on? Even if we know that std::mutex has a deleted copy constructor, why the hell compiler says that our class has a deleted copy constructor? The reason for this is that whenever you put a non-copyable type inside your class and you don’t implement your custom copy constructor, the compiler implicitly deletes the constructor of your class as well. If you think about this, it makes sense bacause if by default it is not known what to do when someone tries to copy std::mutex, it is up to you to decide about it inside your custom copy constructor. Otherwise, such operation is not allowed. Let’s then add our own copy constructor in which we just create a new mutex object:
#include <mutex>
class SomeClass
{
public:
SomeClass(const SomeClass&) : mtx_{} {}
private:
std::mutex mtx_;
};
int main()
{
SomeClass a; // construction
SomeClass b(a); // copy construction
}Note for beginners: I didn’t want to obfuscate this copy constructor example with any additional logic, but if you encounter situation in which you need to copy class holding a mutex, don’t just blindly create a new mutex! Think about the behavior that you expect from the system and whether you really need to copy such class at all. In some cases, std::shared_mutex may come in handy.
When you try to run this code, you will notice that it’s not enough and you get a compilation error – this time the compiler doesn’t now how to construct our class on the first place:
main.cpp: In function ‘int main()’:
main.cpp:14:15: error: no matching function for call to ‘SomeClass::SomeClass()’
14 | SomeClass a; // construction
| ^This is because adding a custom implementation of the copy constructor causes that the compiler stops generating the default constructor (and move constructor) for you class, so you must remember – whenever you add a copy constructor to your class, you must add a default constructor as well:
#include <mutex>
class SomeClass
{
public:
SomeClass() : mtx_{} {}
SomeClass(const SomeClass&) : mtx_{} {}
private:
std::mutex mtx_;
};
int main()
{
SomeClass a; // construction
SomeClass b(a); // copy construction
}Now everything works without an error.
Classes caching values
The third example are classes which are supposed to persist a cache specific for a single class instance. Let’s look at this code:
#include <iostream>
class SomeClass
{
public:
SomeClass() : value_{0}, previous_value_{0} {}
void SetValue(const int new_value)
{
previous_value_ = value_;
value_ = new_value;
}
int GetPreviousValue() const { return previous_value_; }
private:
int value_;
int previous_value_;
};
int main()
{
SomeClass a; // construction
a.SetValue(12);
a.SetValue(24);
SomeClass b(a); // copy construction
std::cout << a.GetPreviousValue() << std::endl; // expected value: 12
std::cout << b.GetPreviousValue() << std::endl; // expected value: 0
}We have here a simple class which holds some current value, but also a value which has been set previously. When we first set on the instance a value 12 and 24 and then call a.GetPreviousValue(), we expect it to return 12. The we construct b by copying a and we call b.GetPreviousValue(). You could expect it to return a default value (in this case it’s 0) because no value has been set for this instance yet. However, the output shows us something different:
12
12We see the value 12 which has been previously set on object a, not on the object b! The autogenerated default copy constructor has just copied everything what’s inside the class because it knows nothing about the logic that we expect from the entire code. Fix for this – adding a custom copy constructor in which you may want to copy the current value, but not the cached value:
#include <iostream>
class SomeClass
{
public:
SomeClass() : value_{0}, previous_value_{0} {}
SomeClass(const SomeClass& other) : value_{other.value_}, previous_value_{0} {}
void SetValue(const int new_value)
{
previous_value_ = value_;
value_ = new_value;
}
int GetPreviousValue() const { return previous_value_; }
private:
int value_;
int previous_value_;
};
int main()
{
SomeClass a; // construction
a.SetValue(12);
a.SetValue(24);
SomeClass b(a); // copy construction
std::cout << a.GetPreviousValue() << std::endl; // expected value: 12
std::cout << b.GetPreviousValue() << std::endl; // expected value: 0
}
Now the output is as expected:
12
0Note for advanced: if you need to implement a caching-like mechanism, consider using
std::weak_ptrfor that purpose.
Read also:
- A 40-line LLM-based bash command executor in Python
- GTest and short-circuit evaluation in C++
- AI is powerful. Snippets are instant.
- From AUTOSAR to S-Core: the first C++ pub/sub implementation
- How to write Arduino Uno code with Python?
- Combining Bazel with Docker
- Running commands with timeout on Linux
- Running Python unit tests with CMake
- Thirdparty dependencies with FetchContent
- Bug of the week #11
- Combining CMake with Docker
- How to search the internet from Linux terminal?
- Folding expressions in C++
- How to derive from an enum in Python?
- Bug of the week #10
- Trying ROS2: client/server within a single container
- Make C++ a better place #4: Go as an alternative
- How to convert hex to dec in Linux terminal?
- Setting up a Python project with CMake
- Separating builds for different configs with Bazel
- Trying ROS2: pub/sub within a single container
- Bug of the week #9
- UDP multicasting with Python
- Destruction order vs thread safety in C++
- Let’s review some code: C++ #2
- Make C++ a better place #3: D as an alternative
- Registering callback using std::function in C++
- Bug of the week #8
- TCP client/server with Python
- Simple menus in Bash scripts with select
- Calling member function on a nullptr in C++
- Bug of the week #7
- Python lru_cache explained
- How to dockerize a Python application?
- Make C++ a better place #2: CppFront as an alternative
- Parameters combinations in GoogleTest
- Data transfer with curl
- Python reduce explained
- Bug of the week #6
- Custom literals in C++
- Linux and hash command
- 5 Python good practices which make life easier
- Let’s review some code: Python #1
- Make C++ a better place #1: What does better mean
- Enums vs enum class in C++
- Bug of the week #5
- UDP client/server with Python
- Hard links in Linux
- Functions calling order in unit tests in C++
- Bug of the week #4
- Yield in Python – state machines, coroutines and more
- Copy files from another branch with Git
- Make C++ a better place #0: Introduction
- 5 misconceptions about std::move in C++
- How to use xargs on Linux?
- How to test method call order with unittest in Python?
- Bug of the week #3
- Build & run C++ unit tests with CMake
- Arrange text with sort on Linux
- Key derivation function with Python









