Constructor Delegation in C++ Explained
Constructor delegation is a C++11 feature that allows a constructor within a class to invoke another constructor from the same class. This article covers the syntax and mechanics of delegating constructors, examines how this technique eliminates duplicate member initialization logic, and highlights the essential rules required to implement it safely and effectively.
The Problem of Repetitive Initialization
Before C++11, a class with multiple overloaded constructors often resulted in duplicated code. When several constructors share the same baseline setup—such as setting default values, validating input, or allocating resources—developers had to repeat the same member initializer lists across every constructor overload.
To avoid this repetition, developers frequently resorted to private
initialization helper functions (e.g., init()):
class MediaPlayer {
private:
std::string filename;
int volume;
bool isLooping;
void init(const std::string& file, int vol, bool loop) {
filename = file;
volume = vol;
isLooping = loop;
// Additional setup logic...
}
public:
MediaPlayer() {
init("default.mp3", 50, false);
}
MediaPlayer(const std::string& file) {
init(file, 50, false);
}
};While helper functions reduce code duplication, they come with significant drawbacks:
- Members are initialized with default values first and then assigned new values inside the helper, adding unnecessary overhead.
constand reference members cannot be initialized via assignment inside a helper function; they must be initialized in the constructor's member initialization list.
What is Constructor Delegation?
Constructor delegation solves this problem by allowing a constructor—known as the delegating constructor—to call another constructor—known as the target constructor—directly in its member initializer list.
Instead of writing custom assignment routines or duplicating initialization lists, the delegating constructor simply forwards arguments to the target constructor, which executes all the setup logic.
Syntax and Implementation
To delegate a constructor, place the target constructor's call inside the member initializer list:
#include <iostream>
#include <string>
class MediaPlayer {
private:
const std::string filename;
int volume;
bool isLooping;
public:
// Target (principal) constructor
MediaPlayer(std::string file, int vol, bool loop)
: filename(std::move(file)), volume(vol), isLooping(loop) {
std::cout << "Target constructor executed.\n";
}
// Delegating constructor: sets default volume and looping
MediaPlayer(std::string file)
: MediaPlayer(std::move(file), 50, false) {
// Optional body execution after target constructor finishes
}
// Delegating constructor: completely default
MediaPlayer()
: MediaPlayer("default.mp3", 50, false) {}
};In this example, the three-parameter constructor serves as the single source of truth for object initialization. The other constructors delegate directly to it, passing default values where appropriate.
How It Reduces Repetitive Code
- Centralized Logic: Resource allocation, validation, and configuration logic reside in one primary constructor. If initialization requirements change, updates only need to occur in that single location.
- Support for
constand References: Because the target constructor uses a standard member initializer list,constfields and reference members are properly initialized without workarounds. - Elimination of Duplication: Overloads no longer need identical member lists, reducing boilerplate code and lowering the risk of forgetting to initialize a newly added member variable across multiple constructors.
Important Rules and Considerations
Exclusive Initializer List: A delegating constructor cannot initialize other members in its own initializer list. The delegation call must be the sole entry in the list:
// Compile Error: cannot mix delegation and member initialization MediaPlayer(std::string file) : MediaPlayer(file, 50, false), volume(100) {}Modifications to members can still occur within the body of the delegating constructor after the target constructor completes.
Prevention of Cycles: Constructors must not delegate to each other in a loop. A recursive delegation cycle (e.g., Constructor A delegates to Constructor B, which delegates back to A) results in a compile-time error.
Object Lifetime: An object is officially considered fully constructed once the target constructor completes execution. If the delegating constructor's body subsequently throws an exception, the class destructor will run for the fully created object.