Article by Ayman Alheraki on January 11 2026 10:33 AM
Object-Oriented Programming (OOP) in C++ has evolved with the language’s modern features, particularly with the introduction of initializer lists and more efficient constructors, including default, copy, and move constructors. These features are crucial for efficient resource management, memory safety, and overall code maintainability. In this article, we will delve into these modern OOP features in C++, explaining their significance with examples.
An initializer list allows you to initialize member variables of a class before the constructor body executes. It is more efficient than assignment in the constructor body, especially for const or reference types, which need to be initialized upon object creation.
Efficiency: Directly initializing variables in the initializer list avoids the overhead of creating a temporary object and then assigning it.
Mandatory for const/reference members: If your class has members that are constants or references, they must be initialized at the point of object creation, and the initializer list is the only way to do that.
class MyClass { const int x; int& y;
public: MyClass(int val, int& ref) : x(val), y(ref) { // constructor body }};Here, x is a constant, and y is a reference. Both must be initialized using the initializer list.
class Point { int x, y;
public: // Initializer list constructor Point(int x_coord, int y_coord) : x(x_coord), y(y_coord) { std::cout << "Point initialized with coordinates (" << x << ", " << y << ")\n"; }};In this example, instead of assigning x_coord and y_coord inside the constructor body, we initialize them directly in the initializer list.
Constructors are critical for object initialization in C++. Modern C++ introduces several types of constructors that help manage resources effectively: default constructors, copy constructors, and move constructors.
A default constructor is a constructor that takes no arguments or has default values for all its parameters. If you don’t define a default constructor, C++ automatically generates one for you.
Example:
class MyClass {public: MyClass() { std::cout << "Default constructor called\n"; }};This constructor initializes objects of MyClass with no input parameters.
The copy constructor initializes an object using another existing object of the same class. It performs a deep copy or shallow copy of the object's members, depending on how you define it.
Default Copy Constructor (generated by C++ automatically):
class MyClass { int x;public: MyClass(int val) : x(val) {}};
// Copy constructor usage:MyClass obj1(10);MyClass obj2 = obj1; // Calls the copy constructorIf your class manages resources like memory (pointers), it’s often necessary to define a custom copy constructor to avoid shallow copying.
class MyClass { int* data;public: MyClass(int val) { data = new int(val); }
// Custom copy constructor MyClass(const MyClass& other) { data = new int(*(other.data)); // Deep copy }
~MyClass() { delete data; // Free memory }};Without this custom copy constructor, both obj1 and obj2 would share the same memory, leading to issues like double deletion when destructors are called.
Introduced in C++11, the move constructor optimizes performance by transferring ownership of resources from one object to another, instead of copying. This is especially useful for classes managing dynamic resources, like memory or file handles.
Move Constructor Syntax:
class MyClass { int* data;public: MyClass(int val) : data(new int(val)) {}
// Move constructor MyClass(MyClass&& other) noexcept : data(other.data) { other.data = nullptr; // Nullify the source object }
~MyClass() { delete data; }};Usage of Move Constructor:
MyClass obj1(10);MyClass obj2 = std::move(obj1); // Moves obj1's data to obj2The move constructor efficiently transfers ownership from obj1 to obj2, and obj1 is left in a safe, valid state (nullptr).
A destructor is called when an object goes out of scope or is deleted. Its main purpose is to free resources that the object may have acquired during its lifetime (e.g., memory, file handles, etc.).
If you don’t provide a destructor, C++ automatically generates one. The default destructor will destroy the object’s members, but if your class manages resources like dynamically allocated memory, it’s essential to define your own.
Example:
class MyClass { int* data;public: MyClass(int val) : data(new int(val)) {}
// Destructor ~MyClass() { delete data; std::cout << "Destructor called, memory freed\n"; }};If you define a destructor, copy constructor, or copy assignment operator, you should define all three. This is known as the "Rule of Three." In modern C++, you should also consider the move constructor and move assignment operator, extending this to the "Rule of Five."
class MyClass { int* data;public: MyClass(int val) : data(new int(val)) {}
// Destructor ~MyClass() { delete data; }
// Copy constructor MyClass(const MyClass& other) : data(new int(*(other.data))) {}
// Copy assignment operator MyClass& operator=(const MyClass& other) { if (this == &other) return *this; // Self-assignment check delete data; // Clean up existing data data = new int(*(other.data)); // Deep copy return *this; }
// Move constructor MyClass(MyClass&& other) noexcept : data(other.data) { other.data = nullptr; }
// Move assignment operator MyClass& operator=(MyClass&& other) noexcept { if (this == &other) return *this; delete data; data = other.data; other.data = nullptr; return *this; }};Efficiency: The move constructor can significantly reduce the overhead of copying objects, especially for large data structures.
Memory Safety: By defining custom constructors and destructors, you can ensure that resources like memory or file handles are properly managed, avoiding leaks or double deletions.
Code Readability: Modern constructors, especially with initializer lists, help make code cleaner and easier to understand.
Rule of Five: Following best practices for defining constructors and destructors ensures that your classes behave as expected in all situations, including copying and moving.
Modern C++ OOP features, such as initializer lists, default, copy, and move constructors, and destructors, provide powerful tools to manage resources and improve program performance. By using these features appropriately, you can write clean, efficient, and safe code in C++. Understanding how to implement and use these features correctly is crucial for leveraging the full power of modern C++ in object-oriented programming.