Default Constructors and Default Arguments

A default constructor is a constructor that can be called with no arguments. It's what runs when you create an object without providing initialization values.

Compiler-generated default constructor

If you don't define any constructors, the compiler generates a default constructor automatically:

class Basic
{
public:
    int data{};
};

int main()
{
    Basic obj{};  // Uses compiler-generated default constructor

    return 0;
}

The compiler-generated default constructor initializes members using their default member initializers or leaves them uninitialized.

Explicitly defining a default constructor

You can define your own default constructor:

class Tracker
{
public:
    int count{};

    Tracker()  // User-defined default constructor
    {
        count = 0;
    }
};

Or with a member initializer list:

class Tracker
{
public:
    int count{};

    Tracker()
        : count{ 0 }
    {
    }
};
Key Concept
The compiler-generated default constructor does the minimum work needed: it calls default constructors for member objects and uses default member initializers for other members. If a member has no default initializer and no default constructor, it remains uninitialized (for fundamental types) or fails to compile (for class types without default constructors).

When the compiler won't generate a default constructor

If you define ANY constructor, the compiler won't generate a default constructor:

class Character
{
public:
    std::string name{};
    int level{};

    Character(std::string n, int l)  // User-defined constructor
        : name{ n }, level{ l }
    {
    }
};

int main()
{
    Character hero{ "Knight", 10 };  // OK
    Character empty{};               // Error: no default constructor!

    return 0;
}

Once you define a constructor, you must explicitly define a default constructor if you want one.

Defining both default and parameterized constructors

You can provide multiple constructors:

class Character
{
public:
    std::string name{};
    int level{};

    Character()  // Default constructor
        : name{ "Unnamed" }, level{ 1 }
    {
    }

    Character(std::string n, int l)  // Parameterized constructor
        : name{ n }, level{ l }
    {
    }
};

int main()
{
    Character unknown{};               // Uses default constructor
    Character hero{ "Warrior", 25 };   // Uses parameterized constructor

    return 0;
}

Default arguments create default constructors

Constructors with default arguments can act as default constructors:

class Box
{
public:
    double width{};
    double height{};

    Box(double w = 1.0, double h = 1.0)
        : width{ w }, height{ h }
    {
    }
};

int main()
{
    Box unit{};              // width=1.0, height=1.0
    Box wide{ 5.0 };         // width=5.0, height=1.0
    Box custom{ 8.0, 4.0 };  // width=8.0, height=4.0

    return 0;
}

This single constructor serves three purposes:

  • Default constructor (no arguments)
  • Partial initialization (one argument)
  • Full initialization (two arguments)
Key Concept
Default arguments in constructors are a powerful technique to reduce code duplication. Instead of writing multiple constructors that do similar things, one constructor with defaults can handle all cases. This follows the DRY (Don't Repeat Yourself) principle.

The = default syntax (Modern C++)

In modern C++, you can explicitly request the compiler-generated default constructor using = default:

class Sample
{
public:
    int value{};

    Sample() = default;  // Use compiler-generated default constructor

    Sample(int v)
        : value{ v }
    {
    }
};

This is useful when you have other constructors but want the default one too.

Best Practice
Prefer = default over an empty constructor body when you want the compiler-generated behavior. It's more explicit about intent and can be more efficient.

Calling default constructors

Correct syntax:

Tracker t1{};   // Calls default constructor (preferred)
Tracker t2;     // Also calls default constructor

Incorrect syntax:

Tracker t3();   // ERROR: This declares a function, not an object!

The last line is C++'s "most vexing parse"—the compiler thinks you're declaring a function that returns a Tracker.

Warning
The most vexing parse is a common pitfall. When you write Tracker t();, it looks like you're creating an object with empty parentheses, but C++ interprets it as a function declaration. Always use braces {} for default construction to avoid this ambiguity.

Default constructors and arrays

Default constructors are required for arrays of objects:

class Slot
{
public:
    int id{};

    Slot()
        : id{ 0 }
    {
    }
};

int main()
{
    Slot slots[5];  // Calls default constructor for each element

    return 0;
}

Without a default constructor, you'd need to initialize each array element explicitly.

When to provide a default constructor

Provide one when:

  • Objects have reasonable default values
  • You want to support arrays
  • Objects will be used in containers (like std::vector)
  • Default initialization makes sense for your type

Skip it when:

  • Objects require specific initialization data
  • Default state doesn't make sense
  • You want to force explicit initialization

Example where default doesn't make sense:

class Account
{
public:
    const int number{};
    int balance{};

    // No default constructor - account number is required
    Account(int accNum)
        : number{ accNum }, balance{ 0 }
    {
    }
};

An account without a number doesn't make sense, so no default constructor is provided.

Best Practice
Use member initializers when possible and let the compiler generate the default constructor with = default. Be explicit about using = default when you want the compiler-generated version. Consider default arguments as one constructor with defaults can replace multiple constructors. Document assumptions if your default constructor makes specific choices about initial values.
class Settings
{
public:
    int volume{};

    Settings()  // Defaults to 50% volume
        : volume{ 50 }
    {
    }
};

Summary

Default constructors: A default constructor is one that can be called with no arguments. It runs when objects are created without providing initialization values.

Compiler-generated: If you don't define any constructors, the compiler automatically generates a default constructor that initializes members using their default member initializers or leaves them uninitialized.

Loss of implicit default constructor: Once you define any constructor, the compiler won't generate a default constructor automatically. You must explicitly define one if needed.

Default arguments create default constructors: Constructors with default arguments for all parameters can act as default constructors, reducing the number of constructors needed.

The = default syntax: Explicitly requesting the compiler-generated version with = default is useful when you have other constructors but also want the default one.

Most vexing parse: Be careful with syntax - Tracker t(); declares a function, not an object. Use Tracker t{} or Tracker t; instead.

Arrays and containers: Default constructors are required for creating arrays of objects or using objects in containers without explicit initialization.

Default constructors are important for flexibility and convenience, but shouldn't be provided when default initialization doesn't make logical sense for your type. Use default arguments strategically to reduce constructor duplication while maintaining clear initialization semantics.