How to implement constructor chaining in C++: Calling one constructor from another
As a C# developer I’m used to running through constructors:
class Test {
public Test() {
DoSomething();
}
public Test(int count) : this() {
DoSomethingWithCount(count);
}
public Test(int count, string name) : this(count) {
DoSomethingWithName(name);
}
}
Is there a way to do this in C++?
I tried calling the Class name and using the ‘this’ keyword, but both fail.
In C++, you can implement constructor chaining using constructor delegation, a feature introduced in C++11. This allows one constructor to delegate initialization responsibilities to another constructor of the same class by calling it in the initializer list, similar to how C# constructor chaining works. The syntax involves placing the target constructor call after the colon in the initializer list before the constructor body.
Contents
- Understanding Constructor Chaining in C++
- C++11 Constructor Delegation Syntax
- Practical Examples of Constructor Delegation
- Best Practices and Considerations
- Alternatives to Constructor Delegation
- Common Pitfalls and Solutions
Understanding Constructor Chaining in C++
In C++, unlike C#, you cannot directly call one constructor from another constructor’s body. However, since C++11, the language introduced constructor delegation (also known as delegating constructors) that provides a similar functionality to C# constructor chaining. This feature allows one constructor to transfer initialization responsibilities to another constructor of the same class.
Before C++11, developers had to use alternative approaches like creating private helper methods or using constructor overloading with default arguments to avoid code duplication. Constructor delegation solves this problem elegantly by allowing constructors to call other constructors in their initializer lists.
The key difference from C# is that in C++, the delegation must occur in the initializer list, not in the constructor body. This is because the constructor body executes after the object has been constructed, whereas the initializer list is part of the construction process.
C++11 Constructor Delegation Syntax
The syntax for constructor delegation follows this pattern:
class ClassName {
public:
// Target constructor (the one being called)
ClassName(Args target_args) : member_initializers { /* ... */ } {
constructor_body();
}
// Delegating constructor (the one doing the calling)
ClassName(Args delegating_args) : ClassName(target_args) {
additional_initialization();
}
};
Here’s the basic structure with a simple example:
class Example {
public:
// Primary constructor
Example(int value) : data(value) {
// Initialization logic
}
// Delegating constructor
Example() : Example(0) {
// Additional initialization for default case
}
private:
int data;
};
When you create an Example object using the default constructor Example(), it will:
- Call
Example(0)first - Execute the primary constructor’s body
- Return to the delegating constructor and execute its body
This creates a chain of constructor calls similar to C# constructor chaining.
Practical Examples of Constructor Delegation
Let’s create a more comprehensive example that demonstrates constructor delegation in a real-world scenario:
class Car {
public:
// Constructor with all parameters
Car(const std::string& make, const std::string& model, int year, double mileage)
: make(make), model(model), year(year), mileage(mileage) {
std::cout << "Car with all parameters constructed\n";
}
// Constructor with required fields only
Car(const std::string& make, const std::string& model)
: Car(make, model, 2024, 0.0) {
std::cout << "Car with basic info constructed\n";
}
// Default constructor
Car() : Car("Unknown", "Unknown Model") {
std::cout << "Default car constructed\n";
}
private:
std::string make;
std::string model;
int year;
double mileage;
};
This example shows a three-level constructor delegation chain:
Car()callsCar("Unknown", "Unknown Model")Car("Unknown", "Unknown Model")callsCar("Unknown", "Unknown Model", 2024, 0.0)- The most detailed constructor performs the actual initialization
Another practical example with more complex initialization:
class DatabaseConnection {
public:
// Full constructor
DatabaseConnection(const std::string& host, int port, const std::string& username,
const std::string& password, const std::string& database)
: host(host), port(port), username(username), password(password),
database(database), connection(nullptr) {
connect();
}
// Constructor with authentication
DatabaseConnection(const std::string& host, int port, const std::string& username,
const std::string& password)
: DatabaseConnection(host, port, username, password, "default_db") {
std::cout << "Connected with authentication\n";
}
// Simple constructor
DatabaseConnection(const std::string& host, int port)
: DatabaseConnection(host, port, "guest", "") {
std::cout << "Connected as guest\n";
}
private:
std::string host;
int port;
std::string username;
std::string password;
std::string database;
void* connection;
void connect() {
// Actual connection logic
}
};
Best Practices and Considerations
When using constructor delegation, keep these important considerations in mind:
1. Constructor Order Matters
The order in which constructors are called follows the delegation chain. The target constructor must be called before the delegating constructor’s body executes.
class Test {
public:
Test(int x) : value(x) {
std::cout << "Primary constructor: " << value << "\n";
}
Test() : Test(42) {
std::cout << "Delegating constructor\n";
}
private:
int value;
};
// Output when creating Test():
// Primary constructor: 42
// Delegating constructor
2. Avoid Constructor Recursion
Be careful not to create circular dependencies between constructors, as this will cause infinite recursion and stack overflow:
// BAD: Avoid this pattern
class BadExample {
public:
BadExample() : BadExample(1) {} // Calls BadExample(1)
BadExample(int x) : BadExample() {} // Calls BadExample() - infinite recursion!
};
3. Member Initialization Rules
Remember that when using constructor delegation, only the target constructor’s member initializers are executed. The delegating constructor’s member initializers are ignored:
class Example {
public:
Example(int x) : data(x) {
std::cout << "Target constructor\n";
}
Example() : Example(10), data(20) { // data(20) is ignored!
std::cout << "Delegating constructor\n";
}
private:
int data;
};
// Output: Target constructor\nDelegating constructor
// Final value of data is 10, not 20
4. Virtual Base Classes and Multiple Inheritance
Constructor delegation works well with single inheritance but requires special attention with multiple inheritance or virtual base classes:
class Base {
public:
Base() { std::cout << "Base\n"; }
Base(int x) : Base() { std::cout << "Base with " << x << "\n"; }
};
class Derived : public Base {
public:
Derived() : Base(42) { std::cout << "Derived\n"; }
Derived(const std::string& name) : Derived() {
std::cout << "Derived with name: " << name << "\n";
}
};
Alternatives to Constructor Delegation
Before C++11, developers used these alternatives to achieve similar functionality:
1. Private Helper Methods
class PreCpp11Example {
public:
PreCpp11Example(int x, int y) {
init(x, y);
}
PreCpp11Example(int x) {
init(x, 0);
}
private:
int data1;
int data2;
void init(int x, int y) {
data1 = x;
data2 = y;
// Common initialization logic
}
};
2. Constructor Overloading with Default Parameters
class DefaultParamExample {
public:
DefaultParamExample(int x, int y = 0, const std::string& name = "default")
: data1(x), data2(y), name(name) {
// Common initialization logic
}
};
3. Template Constructors
class TemplateExample {
public:
template<typename... Args>
TemplateExample(Args... args) {
init(args...);
}
private:
void init() { /* ... */ }
void init(int x) { /* ... */ }
void init(int x, int y) { /* ... */ }
void init(int x, int y, const std::string& name) { /* ... */ }
};
While these approaches work, they’re less elegant than constructor delegation and often lead to more complex code.
Common Pitfalls and Solutions
Pitfall 1: Calling Constructor in Body
Trying to call a constructor in the constructor body creates a temporary object instead of delegating:
// WRONG: Creates temporary object
class WrongExample {
public:
WrongExample() {
WrongExample temp(42); // Creates temporary, doesn't initialize *this
}
WrongExample(int x) : value(x) {}
private:
int value;
};
// Solution: Use constructor delegation in initializer list
class CorrectExample {
public:
CorrectExample() : CorrectExample(42) {} // Proper delegation
CorrectExample(int x) : value(x) {}
private:
int value;
};
Pitfall 2: Member Initializer Confusion
Remember that member initializers in delegating constructors are ignored:
// Confusing but valid
class ConfusingExample {
public:
ConfusingExample() : ConfusingExample(10), value(20) {
// value(20) is ignored!
// Final value is 10, not 20
}
ConfusingExample(int x) : value(x) {}
private:
int value;
};
Pitfall 3: Virtual Function Calls
Avoid calling virtual functions from constructors, as they won’t behave as expected due to the object’s incomplete state:
// Problematic
class Base {
public:
Base() {
virtualFunction(); // Calls Base::virtualFunction(), not Derived's
}
virtual void virtualFunction() = 0;
};
class Derived : public Base {
public:
void virtualFunction() override {
// This won't be called from Base constructor
}
};
Conclusion
Constructor delegation in C++ provides an elegant solution for constructor chaining that was previously impossible. Here are the key takeaways:
- Constructor delegation is the C++ equivalent of C# constructor chaining, introduced in C++11
- Syntax: Place the target constructor call in the initializer list:
ClassName(args) : ClassName(target_args) { ... } - Benefits: Reduces code duplication, improves maintainability, and creates clearer constructor relationships
- Limitations: Cannot call constructors in the constructor body, must avoid recursion, and member initializers in delegating constructors are ignored
- Best practices: Design a clear delegation hierarchy, avoid circular dependencies, and be mindful of the order of execution
For C# developers transitioning to C++, constructor delegation provides a familiar pattern for organizing constructor logic, though with the important constraint that delegation must occur in the initializer list rather than the constructor body.