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.