Skip to content

Constructors — Default, Parameterized, Copy, Move, and Initializer Lists

DodaTech Updated 2026-06-28 8 min read

In this tutorial, you will learn about Constructors. We cover key concepts, practical examples, and best practices to help you master this topic.

C++ constructors are special member functions called when an object is created, supporting default, parameterized, copy, and move initialization with member initializer lists for efficient resource management.

What You'll Learn

You will write default constructors that initialize objects without arguments, parameterized constructors that initialize with user-provided values, copy constructors for copying objects, move constructors (C++11) for transferring resources, use member initializer lists for efficient initialization, understand the compiler-generated default constructor rules, and avoid common pitfalls like double initialization and slicing.

Why It Matters

Constructors are responsible for putting objects into a valid initial state. An object with uninitialized members is a time bomb. Understanding constructors deeply is essential for resource management (RAII) and for writing classes that are safe, efficient, and easy to use. Move constructors in particular are critical for performance with C++ standard library containers.

Learning Path

graph LR
    A["11: Classes & Objects"] --> B["12: Constructors"]
    B --> C["13: Destructors"]
    C --> D["14: Encapsulation"]
    style A fill:#4a90d9,stroke:#2c5f8a,color:#fff
    style B fill:#4a90d9,stroke:#2c5f8a,color:#fff
    style C fill:#4a90d9,stroke:#2c5f8a,color:#fff
    style D fill:#4a90d9,stroke:#2c5f8a,color:#fff

Default Constructor

A constructor that takes no arguments (or all arguments have defaults).

#include <iostream>

class Point {
private:
    int x_, y_;
    
public:
    // Default constructor
    Point() : x_(0), y_(0) {
        std::cout << "Default constructor called\n";
    }
    
    void print() const {
        std::cout << "(" << x_ << ", " << y_ << ")\n";
    }
};

int main() {
    Point p;      // calls default constructor
    p.print();
    
    Point q{};    // also calls default constructor
    q.print();
    
    // Point r();  // WARNING: function declaration, not object!
}

If you do not define any constructors, the compiler generates an implicit default constructor that default-initializes members. The implicit default constructor is deleted if any member has no default constructor.

Parameterized Constructor

#include <iostream>

class Point {
private:
    int x_, y_;
    
public:
    Point() : x_(0), y_(0) {}
    
    // Parameterized constructor
    Point(int x, int y) : x_(x), y_(y) {
        std::cout << "Parameterized constructor called\n";
    }
    
    void print() const {
        std::cout << "(" << x_ << ", " << y_ << ")\n";
    }
};

int main() {
    Point a(3, 4);    // parameterized
    Point b = {5, 6}; // parameterized (brace initialization)
    Point c{7, 8};    // parameterized (preferred)
    
    a.print();
    b.print();
    c.print();
}

Member Initializer List

#include <iostream>
#include <string>

class Person {
private:
    std::string name_;
    int age_;
    const int id_;         // must be initialized in initializer list
    std::string& ref_;     // must be initialized in initializer list
    
public:
    Person(const std::string& name, int age, int id, std::string& ref)
        : name_(name)       // copy from parameter
        , age_(age)          // copy from parameter
        , id_(id)            // const member: must use initializer list
        , ref_(ref)          // reference member: must use initializer list
    {
        // Assignment inside body would be too late for const and ref
        // Also less efficient: default-constructs then assigns
    }
    
    void print() const {
        std::cout << name_ << " (" << age_ << ")\n";
    }
};

int main() {
    std::string global = "GlobalRef";
    Person p("Alice", 30, 1001, global);
    p.print();
}

Always use member initializer lists. They are more efficient than assignment in the constructor body because they initialize members directly instead of default-constructing then assigning.

Members are initialized in declaration order, not initializer list order. This matters when one member depends on another.

Copy Constructor

#include <iostream>

class Vector {
private:
    int* data_;
    size_t size_;
    
public:
    Vector(size_t size) : data_(new int[size]()), size_(size) {
        std::cout << "Construct\n";
    }
    
    // Copy constructor
    Vector(const Vector& other) : data_(new int[other.size_]), size_(other.size_) {
        std::cout << "Copy construct\n";
        for (size_t i = 0; i < size_; ++i) {
            data_[i] = other.data_[i];
        }
    }
    
    ~Vector() {
        delete[] data_;
    }
    
    int& operator[](size_t i) { return data_[i]; }
    const int& operator[](size_t i) const { return data_[i]; }
    size_t size() const { return size_; }
};

int main() {
    Vector v1(5);
    v1[2] = 42;
    
    Vector v2 = v1;    // copy constructor
    v2[2] = 99;
    
    std::cout << v1[2] << " " << v2[2] << "\n";  // 42 99 (independent copies)
    
    Vector v3(v1);     // also copy constructor
}

The copy constructor creates a new object as a copy of an existing one. If you do not define one, the compiler generates a shallow copy (member-wise copy), which is dangerous for classes with raw pointer members.

Move Constructor (C++11)

#include <iostream>
#include <utility>

class Buffer {
private:
    int* data_;
    size_t size_;
    
public:
    Buffer(size_t size) : data_(new int[size]()), size_(size) {
        std::cout << "Construct\n";
    }
    
    // Move constructor: "steal" resources from source
    Buffer(Buffer&& other) noexcept
        : data_(other.data_), size_(other.size_) {
        std::cout << "Move construct\n";
        other.data_ = nullptr;
        other.size_ = 0;
    }
    
    ~Buffer() {
        delete[] data_;
        std::cout << "Destroy\n";
    }
};

Buffer createBuffer() {
    Buffer tmp(1000);
    return tmp;  // move constructor invoked (or RVO)
}

int main() {
    Buffer b = createBuffer();  // moves temporary into b
}

The move constructor transfers resources from a temporary object (rvalue) to a new object, avoiding expensive deep copies. It must leave the source in a valid but unspecified state (typically empty).

= default and = delete

#include <iostream>

class Widget {
public:
    // Use compiler-generated default
    Widget() = default;
    
    // Delete copy (make class non-copyable)
    Widget(const Widget&) = delete;
    Widget& operator=(const Widget&) = delete;
    
    // Allow move
    Widget(Widget&&) = default;
    Widget& operator=(Widget&&) = default;
};

int main() {
    Widget w1;
    // Widget w2 = w1;  // Error: copy deleted
    Widget w3 = std::move(w1);  // OK: move allowed
}

Use = default to explicitly request the compiler-generated version. Use = delete to prohibit a constructor or operator.

Constructor Delegation (C++11)

class Point {
    int x_, y_;
    
public:
    Point() : Point(0, 0) {}  // delegates to two-arg constructor
    
    Point(int x, int y) : x_(x), y_(y) {}
};

One constructor can call another constructor in the same class. This reduces code duplication.

Common Mistakes

Mistake 1: Uninitialized Pointers

class Wrapper {
    int* ptr;
public:
    Wrapper() { /* ptr not initialized */ }
};

Always initialize all members, especially pointers. Use initializer lists.

Mistake 2: Copy Constructor Does Deep Copy

// Shallow copy (compiler-generated) leads to double delete

Always write a custom copy constructor (or delete it) for classes with raw resource handles.

Mistake 3: Move Constructor Not Marked noexcept

Buffer(Buffer&& other) { ... }  // missing noexcept

Standard containers (vector, etc.) use noexcept move constructors to decide whether to move or copy during reallocation.

Mistake 4: Most Vexing Parse

Widget w();  // declares a function, not an object!

Use {} or no parentheses for default construction: Widget w{}; or Widget w;.

Mistake 5: Order of Initialization

Members are initialized in declaration order, not initializer list order.

class Bad {
    int y;
    int x;
public:
    Bad() : x(10), y(x) {}  // y initialized first (y gets garbage, then x=10)
};

Mistake 6: Forgetting to Delete Copy When Managing Raw Resources

If your class manages a raw pointer, file handle, or other resource, either implement the rule of three/five or delete the copy operations.

Practice Questions

  1. What is the difference between Point p; and Point p();?
  2. When must you use a member initializer list instead of assignment in the constructor body?
  3. Write a copy constructor for a class that owns a dynamically allocated array.
  4. What does noexcept on a move constructor enable?
  5. What happens if you do not define any constructor for a class?

Challenge

Implement a DynamicArray class with default constructor, parameterized constructor (size), copy constructor, move constructor, and destructor. Verify deep copy works by modifying elements in the copy without affecting the original.

FAQ

Can a constructor be virtual?

No. Virtual functions require a vtable pointer, which is set up during construction. The base class constructor runs before the derived class vtable exists.

Can a constructor throw an exception?

Yes. If a constructor throws, the object is not considered constructed, and the destructor is not called. Any fully constructed subobjects are destroyed.

What is explicit constructor?

The explicit keyword prevents implicit conversions through the constructor. explicit Vector(int size); prevents Vector v = 5;.

What is the copy-and-swap idiom?

A technique that provides strong exception safety for copy assignment by creating a temporary copy and swapping its contents with *this.

When is a move constructor called?

When initializing an object from an rvalue: returning from a function, using std::move(), or constructing from a temporary.

Can I have both copy and move constructors?

Yes. When both are defined, the appropriate one is chosen based on whether the argument is an lvalue (copy) or rvalue (move).

Mini Project

#include <iostream>
#include <cstring>
#include <utility>

class String {
private:
    char* data_;
    size_t size_;
    
public:
    String() : data_(nullptr), size_(0) {}
    
    String(const char* str) : data_(nullptr), size_(0) {
        size_ = std::strlen(str);
        data_ = new char[size_ + 1];
        std::strcpy(data_, str);
        std::cout << "Construct: " << data_ << "\n";
    }
    
    String(const String& other) : data_(nullptr), size_(other.size_) {
        data_ = new char[size_ + 1];
        std::strcpy(data_, other.data_);
        std::cout << "Copy construct: " << data_ << "\n";
    }
    
    String(String&& other) noexcept
        : data_(other.data_), size_(other.size_) {
        other.data_ = nullptr;
        other.size_ = 0;
        std::cout << "Move construct\n";
    }
    
    ~String() {
        delete[] data_;
        std::cout << "Destroy\n";
    }
    
    void print() const {
        if (data_) std::cout << data_ << "\n";
    }
};

int main() {
    String s1("Hello");
    String s2 = s1;
    String s3 = std::move(s1);
    s2.print();
    s3.print();
}

What's Next

Constructors create objects. The next lesson covers destructors: how objects are destroyed, the RAII concept, and proper resource cleanup patterns.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro