#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

A 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

A 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.