Introduction to Constructors

When you create an object, you often want to initialize it immediately. Constructors provide a way to automatically initialize objects when they're created.

The problem with manual initialization

Without constructors, you must initialize objects manually:

class Enemy
{
public:
    std::string type{};
    int health{};
    int damage{};
};

int main()
{
    Enemy goblin{};
    goblin.type = "Goblin";
    goblin.health = 50;
    goblin.damage = 10;

    return 0;
}

This is tedious and error-prone. What if you forget to set health? The object starts in an incomplete state.

What is a constructor?

A constructor is a special member function that runs automatically when an object is created. It has the same name as the class and no return type:

class Enemy
{
public:
    std::string type{};
    int health{};
    int damage{};

    // Constructor
    Enemy()
    {
        type = "Skeleton";
        health = 75;
        damage = 15;
    }
};

int main()
{
    Enemy monster{};  // Constructor runs automatically!
    // monster is fully initialized

    return 0;
}

When you create monster, the constructor runs immediately, initializing all members.

Constructors with parameters

Constructors can accept arguments, letting you customize initialization:

class Enemy
{
public:
    std::string type{};
    int health{};
    int damage{};

    Enemy(std::string enemyType, int startHealth)
    {
        type = enemyType;
        health = startHealth;
        damage = 10;
    }
};

int main()
{
    Enemy goblin{ "Goblin", 50 };
    Enemy dragon{ "Dragon", 500 };

    return 0;
}

Multiple constructors

Classes can have multiple constructors (overloading):

class Hitbox
{
public:
    int width{};
    int height{};

    // Default constructor
    Hitbox()
    {
        width = 32;
        height = 32;
    }

    // Constructor with parameters
    Hitbox(int w, int h)
    {
        width = w;
        height = h;
    }
};

int main()
{
    Hitbox defaultBox{};           // Uses default constructor
    Hitbox customBox{ 64, 128 };   // Uses parameterized constructor

    return 0;
}

Constructor execution order

Constructors run before any other code can access the object:

#include <iostream>

class Sample
{
public:
    int value{};

    Sample()
    {
        std::cout << "Constructor running\n";
        value = 100;
    }
};

int main()
{
    std::cout << "Before creating object\n";
    Sample obj{};                     // Constructor runs here
    std::cout << "After creating object\n";
    std::cout << obj.value << '\n';

    return 0;
}

Output:

Before creating object
Constructor running
After creating object
100

Why use constructors?

Guaranteed initialization: Objects are always properly initialized.

Convenience: One line creates and initializes an object.

Validation: Check arguments during construction.

class Stats
{
public:
    int strength{};

    Stats(int str)
    {
        if (str >= 1 && str <= 100)
            strength = str;
        else
            strength = 10;  // Default to 10 if invalid
    }
};

Acquisition of resources: Open files, allocate memory, establish connections.

class Connection
{
private:
    bool isConnected{};

public:
    Connection(const std::string& address)
    {
        // Simulate connection setup
        isConnected = true;
    }
};

Direct vs. uniform initialization

You can initialize objects using different syntaxes:

Enemy e1("Orc", 100);      // Direct initialization
Enemy e2{ "Orc", 100 };    // Uniform initialization (preferred)

Uniform initialization (curly braces) is preferred because it:

  • Prevents narrowing conversions
  • Works consistently across all types
  • Is more explicit
Best Practice
Always provide constructors for classes that need initialization. Initialize all members to avoid leaving them in indeterminate states. Validate parameters to check that arguments make sense. Use member initializer lists (covered in the next lesson) as they're more efficient than assignment in the constructor body. Make constructors explicit when appropriate to prevent unexpected implicit conversions.

Summary

Constructors defined: Constructors are special member functions that run automatically when an object is created, having the same name as the class and no return type.

Guaranteed initialization: Constructors ensure objects are always properly initialized, preventing the creation of objects in incomplete or invalid states.

Parameterized constructors: Constructors can accept arguments to customize initialization, allowing objects to be created with specific values in a single step.

Multiple constructors: Classes can have multiple constructors (overloading) to support different initialization scenarios, such as default construction and parameterized construction.

Constructor execution: Constructors run before any other code can access the object, ensuring initialization happens atomically and completely.

Initialization syntax: Objects can be initialized using direct initialization or uniform initialization (curly braces). Uniform initialization is preferred as it prevents narrowing conversions and works consistently across all types.

Constructors are fundamental to creating robust classes. They eliminate manual initialization errors, enable validation during object creation, and ensure objects start their lifetime in valid, well-defined states. Every class that manages data should provide appropriate constructors.