C++ Delegating Constructors
Introduction
When building classes in C++, you often need multiple constructors to initialize objects in different ways. Before C++11, implementing several constructors often led to duplicated code or required separate helper methods to share initialization logic. Delegating constructors, introduced in C++11, solve this problem by allowing one constructor to call another constructor in the same class.
This feature helps you:
- Eliminate code duplication
- Centralize initialization logic
- Create more maintainable and less error-prone code
- Implement the DRY (Don't Repeat Yourself) principle
Basic Syntax
The syntax for delegating constructors is straightforward. A constructor can call another constructor in its initialization list:
class MyClass {
public:
    // Primary constructor
    MyClass(int x, std::string s, bool b) {
        // Full initialization logic here
    }
    
    // Delegating constructor
    MyClass(int x) : MyClass(x, "default", false) {
        // Any additional logic specific to this constructor
    }
};
How Delegating Constructors Work
Let's understand the concept with a simple example:
#include <iostream>
#include <string>
class Person {
private:
    std::string name;
    int age;
    std::string address;
public:
    // Primary constructor
    Person(const std::string& n, int a, const std::string& addr) 
        : name(n), age(a), address(addr) {
        std::cout << "Primary constructor called" << std::endl;
    }
    
    // Delegating constructor 1
    Person(const std::string& n, int a) 
        : Person(n, a, "Unknown") {
        std::cout << "Delegating constructor with two parameters called" << std::endl;
    }
    
    // Delegating constructor 2
    Person() 
        : Person("John Doe", 0) {
        std::cout << "Default constructor called" << std::endl;
    }
    
    void display() const {
        std::cout << "Name: " << name << ", Age: " << age << ", Address: " << address << std::endl;
    }
};
int main() {
    std::cout << "Creating p1:" << std::endl;
    Person p1("Alice", 25, "123 Main St");
    p1.display();
    
    std::cout << "\nCreating p2:" << std::endl;
    Person p2("Bob", 30);
    p2.display();
    
    std::cout << "\nCreating p3:" << std::endl;
    Person p3;
    p3.display();
    
    return 0;
}
Output:
Creating p1:
Primary constructor called
Name: Alice, Age: 25, Address: 123 Main St
Creating p2:
Primary constructor called
Delegating constructor with two parameters called
Name: Bob, Age: 30, Address: Unknown
Creating p3:
Primary constructor called
Delegating constructor with two parameters called
Default constructor called
Name: John Doe, Age: 0, Address: Unknown
Step-by-Step Explanation
- 
The Personclass has three constructors:- The primary constructor that initializes all member variables
- A delegating constructor that only requires name and age, delegating to the primary constructor with a default address
- A default constructor that delegates to the two-parameter constructor with default values
 
- 
When we create p1, only the primary constructor is called since we provide all three parameters.
- 
When we create p2, first the primary constructor is called (through delegation), and then the two-parameter constructor continues with its own body.
- 
When we create p3, the call chain is: default constructor → two-parameter constructor → primary constructor. Each constructor's body executes in the reverse order after the initialization is complete.
Rules and Restrictions
When using delegating constructors, keep these important rules in mind:
- 
No circular delegation: A constructor cannot delegate to itself or create a circular chain of delegations. The compiler will detect this and produce an error. 
- 
Initialization order: The delegated constructor completes its initialization list and body before the delegating constructor's body executes. 
- 
Member initializers: A delegating constructor cannot include member initializers in its initialization list alongside the constructor delegation. 
class Example {
private:
    int a;
    int b;
    
public:
    Example(int x, int y) : a(x), b(y) {}
    
    // INCORRECT - cannot mix delegation with member initialization
    // Example(int x) : Example(x, 0), a(x) {}  // Compiler error
    
    // CORRECT approach
    Example(int x) : Example(x, 0) {
        // Any additional initialization goes here
    }
};
Practical Applications
1. Configuration Class with Default Values
#include <iostream>
#include <string>
class Configuration {
private:
    std::string hostname;
    int port;
    bool useSSL;
    int timeout;
public:
    // Primary constructor with all parameters
    Configuration(const std::string& host, int p, bool ssl, int time) 
        : hostname(host), port(p), useSSL(ssl), timeout(time) {
    }
    
    // Database server configuration
    Configuration(const std::string& host, int p) 
        : Configuration(host, p, true, 30) {
    }
    
    // Default local configuration
    Configuration() 
        : Configuration("localhost", 8080, false, 10) {
    }
    
    void printConfig() const {
        std::cout << "Host: " << hostname << "\n"
                  << "Port: " << port << "\n"
                  << "SSL: " << (useSSL ? "Enabled" : "Disabled") << "\n"
                  << "Timeout: " << timeout << " seconds\n";
    }
};
int main() {
    // Full custom configuration
    Configuration custom("api.example.com", 443, true, 60);
    std::cout << "Custom Configuration:\n";
    custom.printConfig();
    
    // Database server with defaults
    Configuration db("db.example.com", 5432);
    std::cout << "\nDatabase Configuration:\n";
    db.printConfig();
    
    // Default local development configuration
    Configuration local;
    std::cout << "\nLocal Configuration:\n";
    local.printConfig();
    
    return 0;
}
Output:
Custom Configuration:
Host: api.example.com
Port: 443
SSL: Enabled
Timeout: 60 seconds
Database Configuration:
Host: db.example.com
Port: 5432
SSL: Enabled
Timeout: 30 seconds
Local Configuration:
Host: localhost
Port: 8080
SSL: Disabled
Timeout: 10 seconds
2. Resource Management Class
#include <iostream>
#include <string>
#include <vector>
class ResourceManager {
private:
    std::string resourceName;
    std::vector<int> data;
    bool isInitialized;
    
    void logCreation() const {
        std::cout << "Resource '" << resourceName << "' created with " 
                  << data.size() << " elements" << std::endl;
    }
public:
    // Primary constructor
    ResourceManager(const std::string& name, const std::vector<int>& initialData, bool init) 
        : resourceName(name), data(initialData), isInitialized(init) {
        if (isInitialized) {
            std::cout << "Initializing resource '" << resourceName << "'" << std::endl;
            // Imagine complex initialization here
        }
        logCreation();
    }
    
    // Pre-initialized resource with data
    ResourceManager(const std::string& name, const std::vector<int>& initialData) 
        : ResourceManager(name, initialData, true) {
    }
    
    // Empty resource that's initialized
    ResourceManager(const std::string& name) 
        : ResourceManager(name, {}, true) {
    }
    
    // Default resource that's not initialized
    ResourceManager() 
        : ResourceManager("default", {}, false) {
        std::cout << "Warning: Created uninitialized default resource" << std::endl;
    }
    
    void addData(int value) {
        if (!isInitialized) {
            std::cout << "Warning: Adding data to uninitialized resource" << std::endl;
            isInitialized = true;
        }
        data.push_back(value);
    }
    
    void displayInfo() const {
        std::cout << "Resource '" << resourceName << "' has " << data.size() 
                  << " elements and is " << (isInitialized ? "initialized" : "not initialized") 
                  << std::endl;
    }
};
int main() {
    // Using different constructors
    ResourceManager r1("config", {1, 2, 3, 4});
    ResourceManager r2("settings");
    ResourceManager r3;
    
    std::cout << "\nResource status after creation:\n";
    r1.displayInfo();
    r2.displayInfo();
    r3.displayInfo();
    
    std::cout << "\nAdding data to resources:\n";
    r1.addData(5);
    r2.addData(10);
    r3.addData(100);
    
    std::cout << "\nFinal resource status:\n";
    r1.displayInfo();
    r2.displayInfo();
    r3.displayInfo();
    
    return 0;
}
Output:
Initializing resource 'config'
Resource 'config' created with 4 elements
Initializing resource 'settings'
Resource 'settings' created with 0 elements
Resource 'default' created with 0 elements
Warning: Created uninitialized default resource
Resource status after creation:
Resource 'config' has 4 elements and is initialized
Resource 'settings' has 0 elements and is initialized
Resource 'default' has 0 elements and is not initialized
Adding data to resources:
Warning: Adding data to uninitialized resource
Final resource status:
Resource 'config' has 5 elements and is initialized
Resource 'settings' has 1 elements and is initialized
Resource 'default' has 1 elements and is initialized
Advantages of Delegating Constructors
- 
Code Reusability: Common initialization code is written once and reused. 
- 
Reduced Duplication: Eliminates the need to repeat initialization code across multiple constructors. 
- 
Centralized Initialization: All initialization logic can be maintained in one place (usually the most complex constructor). 
- 
Better Maintainability: Changes to initialization logic only need to be made in one place. 
- 
Simplified Constructors: Constructors become shorter and more focused on their specific initialization needs. 
Common Pitfalls and Best Practices
Pitfalls to Avoid
- 
Circular Dependencies: Avoid creating circular chains of constructor delegation. 
- 
Over-delegation: Don't create unnecessarily deep chains of constructor calls. 
- 
Performance Concerns: Be aware that each delegated constructor adds a small overhead. 
Best Practices
- 
Delegate to the Most Complete Constructor: Have all other constructors delegate to the constructor with the most parameters. 
- 
Keep Constructor Bodies Minimal: Ideally, the delegating constructor should only provide default values and minimal additional logic. 
- 
Use Meaningful Default Values: When delegating with default values, make sure they are sensible and well-documented. 
- 
Consider Using Default Arguments: For simple cases, default arguments might be cleaner than multiple delegating constructors. 
Summary
Delegating constructors are a powerful feature in modern C++ that helps reduce code duplication and centralize initialization logic. By allowing one constructor to call another within the same class, you can write more maintainable and less error-prone code.
Key points to remember:
- Use delegation to centralize initialization logic
- The delegated constructor runs before the delegating constructor's body
- You cannot mix member initialization with delegation in the same initialization list
- Avoid circular delegation chains
- Delegate to the most complete constructor for best organization
By mastering delegating constructors, you'll write cleaner, more maintainable C++ code that follows the DRY principle.
Exercises
- 
Create a Rectangleclass with three constructors:- One that takes width and height
- One that takes only one dimension (creating a square)
- A default constructor
 
- 
Implement a NetworkConnectionclass with delegating constructors that handle different connection scenarios (secure/insecure, with/without timeout, etc.).
- 
Refactor the following class to use delegating constructors: class Student {
 private:
 std::string name;
 int id;
 double gpa;
 public:
 Student(std::string n, int i, double g) {
 name = n;
 id = i;
 gpa = g;
 }
 
 Student(std::string n, int i) {
 name = n;
 id = i;
 gpa = 0.0;
 }
 
 Student() {
 name = "Unknown";
 id = 0;
 gpa = 0.0;
 }
 };
Additional Resources
- C++11 Standard
- CPP Reference on Constructors
- Book: "Effective Modern C++" by Scott Meyers (Item 7 covers delegating constructors)
- C++ Core Guidelines
💡 Found a typo or mistake? Click "Edit this page" to suggest a correction. Your feedback is greatly appreciated!