Constructors — Default, Parameterized, Copy, Move, and Initializer Lists
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
- What is the difference between
Point p;andPoint p();? - When must you use a member initializer list instead of assignment in the constructor body?
- Write a copy constructor for a class that owns a dynamically allocated array.
- What does
noexcepton a move constructor enable? - 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
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