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.
  • const and 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

  1. 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.
  2. Support for const and References: Because the target constructor uses a standard member initializer list, const fields and reference members are properly initialized without workarounds.
  3. 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.