Skip to Content
Course content

25: Constructor Overloading and Delegation

Click on the "Edit" button in the top corner of the screen to edit your slide content.

You've likely already encountered the need to provide multiple ways to initialize an object. Maybe your class needs a "default" state, but it also needs to be able to take specific parameters when the caller already has that data. In C++, we handle this with constructor overloading. On the surface, it seems simple: just write another constructor with a different signature. But as your classes grow, you'll find that overloading often leads to a very specific, very annoying kind of code duplication.

The Copy-Paste Trap

Let's look at a GameCharacter class. Every character needs a name, a health value, and a strength rating. In a naive implementation, you might write a "full" constructor and then a few "convenience" constructors for common scenarios—like a default character or a character with just a name.

class GameCharacter {
    std::string name;
    int health;
    int strength;

public:
    // The "Master" constructor
    GameCharacter(std::string n, int h, int s) {
        name = n;
        health = h;
        strength = s;
        std::cout << "Character " << name << " fully initialized.\n";
    }

    // Convenience: Default health and strength
    GameCharacter(std::string n) {
        name = n;
        health = 100; 
        strength = 10;
        std::cout << "Character " << name << " initialized with defaults.\n";
    }

    // Convenience: Totally default
    GameCharacter() {
        name = "Unknown";
        health = 100;
        strength = 10;
        std::cout << "Default character created.\n";
    }
};

At first glance, this works. But look closely at the assignments. I'm repeating the default values (100 and 10) in both the second and third constructors. This is a red flag. If I decide later that the base health should be 150, I have to hunt down every single constructor and update the value manually. It's a tiny mistake to make, but in a production codebase with dozens of fields, it's exactly where bugs hide.

The Maintenance Tax

The problem isn't just the values; it's the logic. Imagine if I added a validation check to ensure health never exceeds 1000. I'd have to paste that if statement into every single constructor. This is what I call the "maintenance tax." You aren't just writing code once; you're signing up to update it in three different places every time the business logic changes.

You might be tempted to move this logic into a private init() method. While that works, it's a bit clunky in C++ because it happens after the members have already been initialized by the compiler. There is a cleaner, more idiomatic way to handle this: Constructor Delegation.

Delegating the Heavy Lifting

Introduced in C++11, constructor delegation allows one constructor to call another constructor from the same class. The trick is that this happens in the initializer list, not in the body of the function. This ensures that the "master" constructor handles all the actual setup, and the convenience constructors simply act as wrappers that pass in default values.

class GameCharacter {
    std::string name;
    int health;
    int strength;

public:
    // This is now our single point of truth
    GameCharacter(std::string n, int h, int s) 
        : name(n), health(h), strength(s) 
    {
        std::cout << "Character " << name << " created.\n";
    }

    // Delegate to the master constructor
    GameCharacter(std::string n) : GameCharacter(n, 100, 10) {
        // Body can be empty or used for additional specific logic
    }

    // Delegate to the master constructor
    GameCharacter() : GameCharacter("Unknown", 100, 10) {
    }
};

I love this approach because it centralizes the initialization logic. If I change the way health is processed, I only change it in one place. The other constructors don't care how the character is built; they just know what values to provide to the builder.

One important rule to remember: you can't have a "circle" of delegation. Constructor A cannot delegate to B if B delegates back to A. The compiler will catch this, but it's worth keeping in mind when you're designing complex inheritance hierarchies. Also, remember that if you delegate, you cannot initialize other members in the initializer list of the delegating constructor. You have to pick one: either you delegate to another constructor, or you initialize members yourself. You can't do both in the same list.




📋 Practical Task

Exercise: Refactoring the NetworkConnection Class

You have been handed a NetworkConnection class that is a maintenance nightmare. It has three constructors that all repeat the same initialization logic for timeout and retryCount. Your task is to refactor this class using Constructor Delegation to ensure there is only one place where the member variables are actually assigned.

Requirements:

  • Create a "Master" constructor that takes ipAddress (string), port (int), timeout (int), and retryCount (int).
  • Create a second constructor that takes only ipAddress and port, delegating to the master constructor with a default timeout of 30 and retry count of 3.
  • Create a third default constructor that delegates to the master constructor with a default IP of "127.0.0.1", port 80, timeout 30, and retry count 3.
  • Ensure that no member variables are assigned inside the curly braces {} of the constructors; use the initializer list for delegation.
// Start with this structure:
class NetworkConnection {
    std::string ipAddress;
    int port;
    int timeout;
    int retryCount;

public:
    // Implement your constructors here
};
Rating
0 0

There are no comments for now.

to be the first to leave a comment.