Constructors in C++: Default, Parameterized, Copy, Move

Getting started with Constructors in C++? This beginner‑friendly guide explains what constructors are, why they matter, and how to use them with clean, well‑indented code, real‑life analogies, and “Try it yourself” challenges after each section. Think of constructors like a product’s setup checklist on the assembly line—every object is born configured and ready to use.

In short, Constructors in C++ are special member functions that run automatically when an object is created, ensuring the object starts life in a valid, predictable state. They can be overloaded, chained, and used to enforce rules and invariants right from the start.

🔹 What Are Constructors in C++?

A constructor has the same name as its class and no return type. It runs automatically when an object is created, initializing members and optionally performing setup work such as validation or resource acquisition. Analogy: when a phone leaves the factory, it already has the OS installed and the initial settings applied—the constructor ensures that “out‑of‑the‑box ready” state.

// Minimal class with a constructor
#include <iostream>
using namespace std;
class Greeter {
public:
    Greeter() {
        cout << "Hello from the constructor!\n";
    }
};
int main() {
    Greeter g; // Constructor runs automatically
}

Output

Hello from the constructor!

Try it yourself

  • Create a class Welcome whose constructor prints a personalized greeting.
  • Add a second object in main() and observe that the message prints twice.

🔹 Key Rules and Syntax for Constructors in C++

  • Constructor name matches the class name exactly and has no return type.
  • Can be overloaded (multiple constructors with different parameters).
  • Often declared public so code can create objects freely.
  • Supports member initializer lists for efficient, correct initialization.

🔹 Default Constructor (No Arguments)

A default constructor takes no parameters. If no constructor is provided, the compiler may generate one automatically. Use it to ensure every object has reasonable default values, avoiding uninitialized data.

#include <iostream>
using namespace std;
class Point {
private:
    int x{0};
    int y{0};
public:
    // Default constructor provided explicitly
    Point() {
        /* x and y already zero-initialized by brace init above */
    }
    void print() const {
        cout << "(" << x << ", " << y << ")\n";
    }
};
int main() {
    Point p;      // calls default constructor
    p.print();    // (0, 0)
}

Output

(0, 0)

Try it yourself

  • Add new members (e.g., label) and give them default values using brace initialization.
  • Remove the explicit constructor and verify the compiler still allows default construction.

🔹 Parameterized Constructors in C++

Parameterized constructors accept arguments to customize initial state. Prefer member initializer lists to assign values efficiently and correctly, especially for constants or references that can’t be assigned later in the body.

#include <iostream>
#include <string>
#include <utility>
using namespace std;
class Book {
private:
    string title;
    string author;
    int pages{0};
public:
    // Member initializer list initializes members directly
    Book(string t, string a, int p)
        : title(move(t)), author(move(a)), pages(p) {
        if (pages <= 0) {
            pages = 1; // basic validation
        }
    }
    void print() const {
        cout << "'" << title << "' by " << author
             << " (" << pages << " pages)\n";
    }
};
int main() {
    Book b("Clean Code", "Robert C. Martin", 464);
    b.print();
}

Output

'Clean Code' by Robert C. Martin (464 pages)

Try it yourself

  • Add validation (e.g., non‑empty title/author) and throw an exception if invalid.
  • Add a default constructor that sets a placeholder title and author.

🔹 Constructor Overloading and the explicit Keyword

Overloading lets a class offer multiple ways to construct objects. Mark single‑parameter constructors as explicit to prevent unexpected implicit conversions that can cause subtle bugs.

#include <string>
#include <iostream>
#include <utility>
using namespace std;
class Url {
private:
    string value;
public:
    Url() : value("about:blank") {}              // default
    explicit Url(string s) : value(move(s)) {}   // prevent implicit conversion
    const string& str() const { return value; }
};
void open(const Url& u) {
    cout << "Opening: " << u.str() << "\n";
}
int main() {
    Url a;  // default
    Url b("https://cpp.dev");
    open(b);
    // open("https://implicit.com"); // ERROR if Url(string) is explicit
    open(Url("https://explicit.com")); // OK
}

Output

Opening: https://cpp.dev
Opening: https://explicit.com

Try it yourself

  • Remove explicit and observe how implicit conversion allows calling open("...") directly.
  • Restore explicit and update call sites to construct Url explicitly.

🔹 Copy Constructor: Cloning Objects Safely

A copy constructor builds a new object as a copy of an existing one. If a class manages resources (memory, file handles), implement a proper copy constructor to avoid shallow copies and double frees. Analogy: making a duplicate key—each copy should operate independently without breaking the other.

#include <iostream>
#include <cstddef>
using namespace std;
class Buffer {
private:
    size_t size{0};
    int* data{nullptr};
public:
    Buffer(size_t n) : size(n), data(new int[n]{}) {}
    // Copy constructor: deep copy
    Buffer(const Buffer& other) 
        : size(other.size), data(new int[other.size]{}) {
        for (size_t i = 0; i < size; ++i)
            data[i] = other.data[i];
    }
    // Destructor
    ~Buffer() {
        delete[] data;
    }
    void set(size_t i, int v) {
        if (i < size) data[i] = v;
    }
    int get(size_t i) const {
        return (i < size) ? data[i] : 0;
    }
};
int main() {
    Buffer a(3);
    a.set(0, 42);
    Buffer b = a; // calls copy constructor
    b.set(0, 7);
    cout << a.get(0) << " vs " << b.get(0) << "\n"; // 42 vs 7
}

Output

42 vs 7

Try it yourself

  • Add a copy assignment operator that performs deep copy and handles self‑assignment safely.
  • Print messages in copy operations to visualize when copying happens.

🔹 Move Constructor: Fast Transfers, No Copies

A move constructor “steals” resources from a temporary (rvalue) object, making transfers cheap. Analogy: instead of photocopying a full library, just hand over the library card. After the move, the source object is left in a valid but unspecified state (often empty).

#include <iostream>
#include <utility>
using namespace std;
class BigData {
private:
    int* data{nullptr};
    size_t n{0};
public:
    BigData(size_t count) : data(new int[count]{}), n(count) {}
    // Move constructor
    BigData(BigData&& other) noexcept 
        : data(other.data), n(other.n) {
        other.data = nullptr;
        other.n = 0;
    }
    // Destructor
    ~BigData() {
        delete[] data;
    }
    size_t size() const {
        return n;
    }
};
BigData makeBig() {
    BigData tmp(1'000'000);
    return tmp; // NRVO + move
}
int main() {
    BigData a = makeBig();          // moved efficiently
    BigData b = BigData(500'000);   // move from temporary
    cout << a.size() << ", " << b.size() << "\n";
}

Output

1000000, 500000

Try it yourself

  • Add a move assignment operator that releases current resources and takes over the source’s.
  • Print diagnostic messages in move operations to confirm when moves occur.

🔹 Delegating Constructors in C++

Delegating constructors let one constructor call another in the same class to avoid duplicated initialization logic. Analogy: different “quick setup” buttons that all route through a master setup path behind the scenes.

#include <string>
#include <iostream>
#include <utility>
using namespace std;
class Profile {
private:
    string name;
    int age;
public:
    Profile() : Profile("Unknown", 0) {}       // delegate to main constructor
    Profile(string n) : Profile(move(n), 0) {} // delegate
    Profile(string n, int a) : name(move(n)), age(a) {}
    void print() const {
        cout << name << " (" << age << ")\n";
    }
};
int main() {
    Profile a, b("Ava"), c("Noah", 21);
    a.print();
    b.print();
    c.print();
}

Output

Unknown (0)
Ava (0)
Noah (21)

Try it yourself

  • Add validation to the main constructor only; confirm all delegated constructors inherit the checks.
  • Add a third delegating path (e.g., age only) that sets a default name.

🔹 Order of Initialization and Best Practices

  • Members initialize in the order they are declared in the class, not the order in the initializer list.
  • Base classes construct before derived classes; destructors run in reverse order.
  • Prefer member initializer lists for constants, references, and performance.
#include <iostream>
using namespace std;
struct Demo {
    int a;
    int b;
    // Even if listed b then a, the real order is declaration order: a then b
    Demo() : b(2), a(1) {
        cout << "a=" << a << ", b=" << b << "\n";
    }
};
int main() {
    Demo d;
}

Output

a=1, b=2

Tip: declare members in the natural initialization order, and mirror that order in your initializer lists for clarity and to avoid warnings.

🔹 Constructors and Inheritance

Base class constructors run before derived class constructors. Derived constructors can explicitly call a base constructor in their initializer list. Constructors can also be inherited with using Base::Base; when appropriate.

#include <iostream>
#include <string>
using namespace std;
class Base {
public:
    Base() {
        cout << "Base()\n";
    }
    Base(string info) {
        cout << "Base(" << info << ")\n";
    }
};
class Derived : public Base {
public:
    Derived() : Base("from Derived default") {
        cout << "Derived()\n";
    }
};
int main() {
    Derived d; // Base(...), then Derived()
}

Output

Base(from Derived default)
Derived()

🔹 Exception Safety in Constructors (RAII)

If a constructor throws, the object isn’t created, and already‑constructed members are cleaned up. Acquire resources in constructors and release them in destructors (RAII). Prefer smart pointers to avoid leaks and ensure strong exception safety.

#include <memory>
#include <stdexcept>
#include <iostream>
using namespace std;
class Widget {
private:
    unique_ptr<int[]> data;
public:
    Widget(size_t n) : data(make_unique<int[]>(n)) {
        if (n == 0)
            throw invalid_argument("Size must be > 0");
    }
    size_t size() const {
        return 0; // placeholder
    }
};
int main() {
    try {
        Widget w(0); // throws
    } catch (const exception& e) {
        cout << "Caught: " << e.what() << "\n";
    }
}

Output

Caught: Size must be > 0

🔹 Deleted, Private, and Factory Constructors

Sometimes, construction should be restricted. Delete unwanted constructors (e.g., copying), or make constructors private and expose a factory method to control how objects are created (common in singletons or resource pools).

#include <memory>
class NonCopyable {
public:
    NonCopyable() = default;
    NonCopyable(const NonCopyable&) = delete;            // forbid copy
    NonCopyable& operator=(const NonCopyable&) = delete; // forbid copy assign
};
class Singleton {
private:
    Singleton() = default; // private constructor
public:
    static Singleton& instance() {
        static Singleton s; // created on first call, thread-safe since C++11
        return s;
    }
};
int main() {
    auto& s1 = Singleton::instance();
    auto& s2 = Singleton::instance();
    (void)s1;
    (void)s2; // same instance
}

Output

No output

🔹 Mini Project: A Configurable Timer (RAII + Overloaded Constructors)

Let’s combine Constructors in C++ concepts: defaults, parameterized, delegating, and RAII. The class automatically starts timing on construction and can be customized via overloaded constructors.

#include <chrono>
#include <iostream>
#include <string>
#include <utility>
using namespace std;
class Timer {
private:
    string label;
    chrono::steady_clock::time_point start;
public:
    Timer() : Timer("Timer") {} // delegate
    explicit Timer(string name)
        : label(move(name)), start(chrono::steady_clock::now()) {
        cout << "[" << label << "] started\n";
    }
    ~Timer() {
        using namespace chrono;
        auto end = steady_clock::now();
        auto ms = duration_cast<milliseconds>(end - start).count();
        cout << "[" << label << "] elapsed: " << ms << " ms\n";
    }
};
int main() {
    {
        Timer t1; // default label
    }
    {
        Timer t2("LoadData"); // custom label
        // simulate work...
    }
}

Output

[Timer] started
[Timer] elapsed: 0 ms
[LoadData] started
[LoadData] elapsed: 0 ms

Try it yourself

  • Add an option to start paused via a boolean constructor parameter.
  • Provide a reset(newLabel) method to restart timing with a different label.

🔹 Best Practices for Constructors in C++

  • Initialize members in the initializer list; prefer brace initialization.
  • Validate inputs in constructors; throw on invalid to keep objects always valid.
  • Use explicit for single‑argument constructors to prevent implicit conversions.
  • Implement copy/move constructors and assignments correctly for resource‑owning types (Rule of Three/Five).
  • Favor RAII with smart pointers over raw new/delete.
  • Keep constructors lightweight; push heavy logic to dedicated methods when possible.

🔹 Common Pitfalls to Avoid

  • Assigning in the constructor body when a member initializer list is required (e.g., for const or references).
  • Forgetting the order of member initialization is declaration order, not initializer‑list order.
  • Shallow copying resource owners, causing double frees or leaks.
  • Omitting explicit and allowing surprising implicit conversions.
  • Throwing after partially allocating resources without RAII safeguards.

🔹 FAQs: Constructors in C++

Q1. Can constructors return values?
No. Constructors have no return type and cannot return values; they initialize objects during creation.

Q2. What is the difference between initializing in the body vs initializer list?
The initializer list constructs members directly (required for const and references). Assigning in the body runs after construction and may be less efficient.

Q3. Can constructors be virtual?
No. Constructors can’t be virtual. Virtual dispatch begins only after construction completes; destructors, however, should often be virtual in polymorphic bases.

Q4. What are Rule of Three and Rule of Five?
If a class defines a destructor, copy constructor, or copy assignment, it likely needs all three (Rule of Three). In C++11+, include move constructor and move assignment as well (Rule of Five). If no resources are owned, prefer the Rule of Zero.

Q5. What is a delegating constructor and when should it be used?
It’s a constructor that calls another constructor in the same class to reuse initialization logic. Use it to avoid duplicating checks and setup across overloads.

Q6. How to prevent copying?
Mark the copy constructor and copy assignment operator as = delete, or manage ownership with smart pointers that express unique/shared semantics explicitly.

🔹 Wrapping Up

Mastering Constructors in C++ helps create safe, clear, and efficient classes. Use initializer lists, validate inputs, mark single‑arg constructors explicit, and embrace RAII. Work through the “Try it yourself” tasks to turn these patterns into second nature.

Leave a Comment

About RadiantRiva

Your go-to resource for coding tutorials, developer guides, and programming tips.

Learn More

Quick Links

Follow Us

Newsletter

Get coding tips, tutorials, and updates straight to your inbox.