Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Constructor Delegation for Code Reuse
Avoid code duplication by having one constructor call another.
Delegating Constructors
When a class has multiple constructors, they often contain duplicate initialization code. Delegating constructors let one constructor call another, reducing duplication.
The duplication problem
Consider a class with multiple constructors:
class Monster
{
private:
std::string type{};
int health{};
int damage{};
public:
Monster()
{
type = "Goblin";
health = 50;
damage = 10;
}
Monster(std::string t)
{
type = t;
health = 50; // Duplicated
damage = 10; // Duplicated
}
Monster(std::string t, int h, int d)
{
type = t;
health = h;
damage = d;
}
};
The initialization logic is repeated across constructors. If you need to change the default values, you must update multiple places.
Delegating constructors
One constructor can delegate to another using a member initializer list:
class Monster
{
private:
std::string type{};
int health{};
int damage{};
public:
Monster(std::string t, int h, int d) // Primary constructor
: type{ t }, health{ h }, damage{ d }
{
}
Monster() // Delegates to primary
: Monster("Goblin", 50, 10)
{
}
Monster(std::string t) // Delegates to primary
: Monster(t, 50, 10)
{
}
};
The first constructor does the actual initialization. The other constructors delegate to it, providing default values for some parameters.
How delegation works
When a constructor delegates:
- The delegated-to constructor runs completely (including its body)
- Then the delegating constructor's body runs (if it has one)
#include <iostream>
class Test
{
public:
int value{};
Test(int v)
: value{ v }
{
std::cout << "Primary constructor\n";
}
Test()
: Test(100)
{
std::cout << "Delegating constructor\n";
}
};
int main()
{
Test obj{};
return 0;
}
Output:
Primary constructor
Delegating constructor
You can't do both
A constructor can either delegate OR use a member initializer list, but not both:
class Data
{
public:
int x{};
int y{};
Data(int a, int b)
: x{ a }, y{ b }
{
}
Data()
: Data(1, 2), x{ 5 } // ERROR: Can't mix delegation and initialization
{
}
};
When delegating, all initialization happens in the delegated-to constructor.
Practical example
class Square
{
private:
double side{};
double area{};
public:
// Master constructor with validation
Square(double s)
: side{}, area{}
{
if (s > 0) side = s;
else side = 1.0;
area = side * side;
}
Square() // Unit square
: Square(1.0)
{
}
};
Validation logic exists in one place. All constructors benefit from it.
Chaining delegations
Constructors can chain:
class Options
{
private:
int option1{};
int option2{};
int option3{};
public:
Options(int o1, int o2, int o3)
: option1{ o1 }, option2{ o2 }, option3{ o3 }
{
}
Options(int o1, int o2)
: Options(o1, o2, 0)
{
}
Options(int o1)
: Options(o1, 0) // Delegates to constructor above
{
}
Options()
: Options(0) // Delegates to constructor above
{
}
};
Each constructor delegates to the next more specific one, forming a chain.
When to use delegating constructors
Use delegation when:
- Multiple constructors have overlapping initialization
- You have validation or setup logic to centralize
- You want to ensure consistent initialization
Example with setup logic:
class ServerConnection
{
private:
std::string address{};
int port{};
bool connected{};
void establish()
{
// Complex connection logic
connected = true;
}
public:
ServerConnection(std::string addr, int p)
: address{ addr }, port{ p }, connected{ false }
{
establish();
}
ServerConnection()
: ServerConnection("localhost", 8080)
{
// establish() already called by delegated constructor
}
};
Avoiding recursion
Don't create circular delegation:
class Broken
{
public:
int value{};
Broken()
: Broken(0) // Calls Broken(int)
{
}
Broken(int v)
: Broken() // Calls Broken() -- INFINITE RECURSION!
{
}
};
The compiler will likely catch this, but it's a logical error to avoid.
Designate a master constructor that performs actual initialization, with others delegating to it. Keep delegation simple - don't create long chains, two levels is usually sufficient. Add logic in delegating constructors sparingly since it happens after the delegated-to constructor completes.
class FileLogger
{
private:
std::string filename{};
public:
FileLogger(std::string file)
: filename{ file }
{
std::cout << "Log file: " << filename << '\n';
}
FileLogger()
: FileLogger("output.log")
{
std::cout << "Using default log file\n"; // Runs after delegated constructor
}
};
Summary
Delegating constructors: One constructor can delegate to another using a member initializer list, allowing code reuse and reducing duplication across multiple constructors.
Execution order: When a constructor delegates, the delegated-to constructor runs completely (including its body), then the delegating constructor's body runs.
Cannot mix delegation and initialization: A constructor can either delegate to another constructor OR use a member initializer list, but not both. All initialization must happen in the delegated-to constructor.
Chaining delegations: Constructors can chain, with each delegating to a more specific one, but avoid creating overly long chains that make code hard to follow.
Centralizing logic: Delegating constructors are ideal for centralizing validation, setup logic, and ensuring consistent initialization across different construction paths.
Avoiding recursion: Never create circular delegation where constructors call each other, as this creates infinite recursion.
Delegating constructors are a powerful tool for reducing code duplication while maintaining flexibility in how objects can be constructed. By designating one master constructor with the core initialization logic, you ensure consistency and make maintenance easier.
Constructor Delegation for Code Reuse - Quiz
Test your understanding of the lesson.
Practice Exercises
Constructor Delegation
Use delegating constructors to reduce code duplication between constructors.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!