#114 – Default, parameterized, overloaded, and delegating constructors

A constructor defines how an object is created.

That matters because construction is the first chance a class has to put itself into a valid state. C++ supports several constructor patterns, and each one solves a slightly different problem.

Three things to take away:

  • A default constructor lets an object be created with no arguments.
  • Parameterized constructors let callers provide initial values.
  • Overloaded and delegating constructors let a class support multiple construction paths without duplicating setup logic.

1. Default constructors

default constructor is a constructor that can be called with no arguments.

#include <iostream>
#include <string>

class Window {
private:
    int m_width {};
    int m_height {};
    std::string m_title {};

public:
    Window()
        : m_width { 800 }
        , m_height { 600 }
        , m_title { "Untitled" }
    {
        std::cout << "Created " << m_title << ": "
                  << m_width << "x" << m_height << '\n';
    }
};

int main(){
    Window window {};
}

Terminal output:
Created Untitled: 800x600

This line calls the default constructor:

Window window {};

The caller provides no arguments, so the class chooses its own default state.

NOTE: If a default constructor isn’t explicitly created, the compiler automatically creates one for you.

2. Parameterized constructors

parameterized constructor accepts arguments.

Use this when the caller should provide the information needed to create the object.

#include <iostream>
#include <string>
#include <string_view>

class Window {
private:
    int m_width {};
    int m_height {};
    std::string m_title {};

public:
    Window(int width, int height, std::string_view title)
        : m_width { width }
        , m_height { height }
        , m_title { title }
    {
        std::cout << "Created " << m_title << ": "
                  << m_width << "x" << m_height << '\n';
    }
};

int main(){
    Window editor { 1280, 720, "Editor" };
}

Terminal output:
Created Editor: 1280x720

This constructor forces the caller to provide all required values:

Window editor { 1280, 720, "Editor" };

That is useful when an object should not exist without meaningful data.

3. Constructors with default arguments

A constructor can also have default arguments.

This can make one constructor serve both the default and customized cases:

#include <iostream>
#include <string>
#include <string_view>

class Window {
private:
    int m_width {};
    int m_height {};
    std::string m_title {};

public:
    Window(int width = 800, int height = 600, std::string_view title = "Untitled")
        : m_width { width }
        , m_height { height }
        , m_title { title }
    {
        std::cout << "Created " << m_title << ": "
                  << m_width << "x" << m_height << '\n';
    }
};

int main(){
    Window defaultWindow {};
    Window editor { 1280, 720, "Editor" };
}

Terminal output:
Created Untitled: 800x600
Created Editor: 1280x720

Because every parameter has a default argument, this constructor can be called with no arguments:

Window defaultWindow {};

That means it acts as a default constructor.

It can also be called with explicit arguments:

Window editor { 1280, 720, "Editor" };

This style is useful when a class has sensible defaults, but still allows customization.

4. Overloaded constructors

Constructors are functions, so they can be overloaded.

That means a class can provide multiple construction paths with different parameter lists.

#include <iostream>
#include <string>
#include <string_view>

class Window {
private:
    int m_width {};
    int m_height {};
    std::string m_title {};

public:
    Window()
        : m_width { 800 }
        , m_height { 600 }
        , m_title { "Untitled" }
    {
        std::cout << "Default window created\n";
    }

    Window(int width, int height)
        : m_width { width }
        , m_height { height }
        , m_title { "Untitled" }
    {
        std::cout << "Sized window created\n";
    }

    Window(int width, int height, std::string_view title)
        : m_width { width }
        , m_height { height }
        , m_title { title }
    {
        std::cout << "Named window created\n";
    }

    void print() const{
        std::cout << m_title << ": "
                  << m_width << "x" << m_height << '\n';
    }
};

int main(){
    Window a {};
    Window b { 1024, 768 };
    Window c { 1920, 1080, "Game" };

    a.print();
    b.print();
    c.print();
}

Terminal output:
Default window created
Sized window created
Named window created
Untitled: 800x600
Untitled: 1024x768
Game: 1920x1080

The compiler chooses the constructor based on the arguments:

Window a {};                     // Window()
Window b { 1024, 768 };          // Window(int, int)
Window c { 1920, 1080, "Game" }; // Window(int, int, std::string_view)

This is useful when the class has genuinely different ways to be created.

5. Delegating constructors

Overloaded constructors can create duplicated initialization logic.

C++ lets one constructor delegate to another constructor in the same class.

#include <iostream>
#include <string>
#include <string_view>

class Window {
private:
    int m_width {};
    int m_height {};
    std::string m_title {};

public:
    Window(int width, int height, std::string_view title)
        : m_width { width }
        , m_height { height }
        , m_title { title }
    {
        std::cout << "Window created\n";
    }

    Window()
        : Window { 800, 600, "Untitled" }
    {
    }

    Window(int width, int height)
        : Window { width, height, "Untitled" }
    {
    }

    void print() const{
        std::cout << m_title << ": "
                  << m_width << "x" << m_height << '\n';
    }
};

int main(){
    Window a {};
    Window b { 1024, 768 };
    Window c { 1920, 1080, "Game" };

    a.print();
    b.print();
    c.print();
}

Terminal output:
Window created
Window created
Window created
Untitled: 800x600
Untitled: 1024x768
Game: 1920x1080

The full constructor performs the actual initialization:

Window(int width, int height, std::string_view title)

The smaller constructors delegate to it:

Window()
    : Window { 800, 600, "Untitled" } { }

This avoids repeating the same member initialization logic in every constructor.