Constructors & Destructors
Learn how C++ objects come into existence fully initialized and clean up automatically when their lifetime ends — constructors, destructors, initializer lists, and = default / = delete.
A constructor is a special member function that runs automatically when an object is created, and is responsible for giving that object a valid initial state. A destructor is a special member function that runs automatically when an object's lifetime ends, and is responsible for any cleanup that object's state requires. Together, constructors and destructors let a class guarantee that every object it produces starts out properly initialized and is cleaned up without the class's users having to remember to do either step by hand.
What Is a Constructor?
A constructor is a member function with the same name as the class, with no return type (not even void), that is invoked automatically when an object of the class is created.
Point() is a constructor: it has no return type and shares the class's name. It runs automatically every time a Point object is created, before the object is used.
Creating p invokes Point's constructor, which sets x and y to 0.0 before p is available for use.
class Point
{
public:
Point()
{
x = 0.0;
y = 0.0;
}
double x;
double y;
};The Implicitly-Generated Default Constructor
If a class declares no constructors at all, the compiler implicitly generates a default constructor — one callable with no arguments. It default-initializes each data member, which for built-in types such as double or int leaves the member with an indeterminate value unless the member has its own default member initializer.
Because Point declares no constructors, the compiler supplies an implicit default constructor. It does not zero-initialize x and y — without an explicit default member initializer or a user-defined constructor, their values are indeterminate until assigned.
class Point
{
public:
double x;
double y;
};
Point p; // implicitly default-constructedUser-Defined Default Constructors
A class can define its own default constructor — one that takes no arguments — to guarantee a well-defined initial state instead of relying on the implicit one.
Every Counter object is guaranteed to start with count equal to 0, because the user-defined default constructor sets it explicitly.
class Counter
{
public:
Counter()
{
count = 0;
}
int count;
};Parameterized Constructors
A constructor can take parameters, letting the caller supply initial values when the object is created.
p is created by calling the two-argument constructor, which sets x to 3.0 and y to 4.0.
class Point
{
public:
Point(double xValue, double yValue)
{
x = xValue;
y = yValue;
}
double x;
double y;
};
Point p(3.0, 4.0);Constructor Syntax Rules
A constructor's name must exactly match its class's name, it declares no return type, and a class may declare several constructors as long as their parameter lists differ (overloading). A constructor cannot be declared const, virtual, or static.
Constructor Invocation Forms
An object can be initialized in several syntactic forms: direct initialization, copy initialization, and list (brace) initialization.
All three forms here select the same two-argument constructor and produce an equivalent Point object; they differ in syntax and in some conversion rules the compiler applies — list initialization, for instance, does not allow narrowing conversions.
Point a(3.0, 4.0); // direct initialization
Point b = Point(3.0, 4.0); // copy initialization from a temporary
Point c{ 3.0, 4.0 }; // list initializationMember Initialization: Body Assignment vs Initializer List
A constructor can give data members their initial values either by assigning to them in the constructor body, or by using a member initializer list before the body. These are not equivalent: assignment in the body means the member was already default-initialized first and is then assigned a new value, while an initializer list initializes the member directly with the given value.
For simple built-in members like double, the practical difference is small, but the distinction matters a great deal for const members, reference members, and class-type members, covered next.
class Point
{
public:
Point(double xValue, double yValue)
{
x = xValue; // x is default-initialized, then assigned
y = yValue;
}
double x;
double y;
};Member Initializer Lists in Depth
A member initializer list is written after a colon following the constructor's parameter list, and initializes members directly rather than default-initializing them first.
x and y are initialized directly from xValue and yValue — no separate default-initialization step happens first. This distinction is required for const data members and reference data members, which can only be initialized, not assigned, and it generally avoids unnecessary work for class-type members.
class Point
{
public:
Point(double xValue, double yValue)
: x(xValue), y(yValue)
{
}
double x;
double y;
};Initialization Order Follows Declaration Order
Data members are always initialized in the order they are declared inside the class, not the order they appear in the initializer list. Writing the initializer list out of declaration order does not change the actual initialization order, and many compilers warn about the mismatch.
Even though high(highValue) appears first in the initializer list, low is declared first in the class, so low is initialized before high at runtime. Writing the list in declaration order avoids this kind of confusion.
class Range
{
public:
Range(int lowValue, int highValue)
: high(highValue), low(lowValue) // written high-then-low
{
}
int low; // declared first
int high; // declared second
};Delegating Constructors
A constructor can call another constructor of the same class as part of its initializer list, instead of initializing members directly. This is called delegation, and lets shared initialization logic live in one place.
The default constructor delegates to the two-argument constructor instead of repeating x(0.0), y(0.0) itself. A constructor that delegates cannot also list its own data members in its initializer list — it may only delegate.
class Point
{
public:
Point()
: Point(0.0, 0.0)
{
}
Point(double xValue, double yValue)
: x(xValue), y(yValue)
{
}
double x;
double y;
};explicit Constructors
By default, a constructor callable with a single argument defines an implicit conversion from that argument's type to the class type. Marking such a constructor explicit disables that implicit conversion, allowing it to be used only for direct or list initialization, not for implicit conversions.
Without explicit, the line Meters m3 = 5.0; would silently construct a Meters from a plain double. Marking the constructor explicit forces the conversion to be written out, which helps catch accidental conversions.
class Meters
{
public:
explicit Meters(double value)
: value(value)
{
}
double value;
};
Meters m1(5.0); // OK: direct initialization
Meters m2 = Meters(5.0); // OK: explicit construction of the temporary
// Meters m3 = 5.0; // Error: explicit disallows this implicit conversionConstructor Overloading
A class may declare several constructors with different parameter lists; the compiler selects the best match at each call site, the same way it selects among overloaded ordinary functions.
Each construction expression selects the overload whose parameter list matches the arguments provided.
class Rectangle
{
public:
Rectangle()
: width(1.0), height(1.0)
{
}
explicit Rectangle(double side)
: width(side), height(side)
{
}
Rectangle(double w, double h)
: width(w), height(h)
{
}
double width;
double height;
};
Rectangle a; // Rectangle()
Rectangle b(5.0); // Rectangle(double)
Rectangle c(3.0, 4.0); // Rectangle(double, double)What Is a Destructor?
A destructor is a special member function, named ~ClassName, that takes no parameters and returns nothing, and runs automatically when an object's lifetime ends. Its purpose is to release any resources the object was responsible for, or otherwise perform final cleanup.
The constructor prints a message when a Logger object is created; the destructor prints a message when it is destroyed.
class Logger
{
public:
Logger()
{
std::cout << "Logger created\n";
}
~Logger()
{
std::cout << "Logger destroyed\n";
}
};Destructor Syntax
A class has at most one destructor; it cannot be overloaded and cannot take parameters, and its name is always ~ followed by the class name. It is invoked automatically — a program almost never needs to call a destructor directly.
Automatic Destructor Invocation
An object with automatic storage duration has its destructor invoked automatically when it goes out of scope, with no explicit action from the program. Objects created with new have dynamic storage duration and require an explicit delete to invoke their destructor — that mechanism belongs to the Dynamic Memory topic.
The destructor call is inserted by the compiler at the closing brace of the block that log is local to — no delete or explicit cleanup call is written by the programmer.
The program's output, in the order the constructor, the body, and finally the destructor run.
void run()
{
Logger log;
std::cout << "Working\n";
} // log's destructor runs here, automatically, as the block endsLogger created
Working
Logger destroyedConstruction and Destruction Order
When multiple objects with automatic storage duration are declared in the same scope, they are constructed in the order they are declared and destroyed in the reverse order — the last object constructed is the first one destroyed.
first, second, and third are constructed top-to-bottom, in declaration order.
As run's scope ends, the objects are destroyed bottom-to-top — the reverse of construction order.
class Logger
{
public:
explicit Logger(std::string label) : label(label)
{
std::cout << "Created " << label << '\n';
}
~Logger()
{
std::cout << "Destroyed " << label << '\n';
}
std::string label;
};
void run()
{
Logger first("first");
Logger second("second");
Logger third("third");
}Created first
Created second
Created third
Destroyed third
Destroyed second
Destroyed firstResource Cleanup Concept
A destructor's most important use is releasing resources an object acquired during its lifetime — dynamically allocated memory, open file handles, and similar. Tying a resource's release to an object's destructor, so that resource management happens automatically without hand-written cleanup calls, is the foundation of RAII (Resource Acquisition Is Initialization), covered in depth in its own topic. How copying and destroying such a class should interact together is covered in the Rule of Zero/Three/Five topic.
Buffer acquires an array in its constructor and releases it in its destructor, so every Buffer object automatically cleans up its own memory when it is destroyed. This class is intentionally minimal — it does not yet define correct copy behavior, which is exactly the concern the Copy Semantics topic addresses in depth.
class Buffer
{
public:
explicit Buffer(int sizeValue)
: size(sizeValue), data(new int[sizeValue])
{
}
~Buffer()
{
delete[] data;
}
int size;
int* data;
};= default
The = default specifier tells the compiler to generate a member function's ordinary compiler-provided implementation explicitly, even when a different constructor is also declared, or simply to state the intent to use the default clearly.
Declaring Point(double, double) would normally suppress the compiler's implicitly-generated default constructor. Writing Point() = default; explicitly asks the compiler to generate the ordinary default constructor anyway, rather than writing its body by hand.
= default also works for a destructor, stating explicitly that the compiler-generated destructor (which does nothing beyond destroying members) is exactly what is wanted.
class Point
{
public:
Point() = default;
Point(double xValue, double yValue) : x(xValue), y(yValue) {}
double x = 0.0;
double y = 0.0;
};class Widget
{
public:
~Widget() = default;
};= delete
The = delete specifier marks a member function as deleted: any attempt to use it is a compile error. This is commonly used to prevent a particular way of constructing an object.
Because the default constructor is deleted, every NonDefault object must be created with an int argument.
class NonDefault
{
public:
NonDefault() = delete;
NonDefault(int value) : value(value) {}
int value;
};
// NonDefault n; // Error: default constructor is deleted
NonDefault n(5); // OKBest Practices
1. Prefer member initializer lists over assignment in the constructor body — they initialize members directly and are required for const and reference members.
2. Write the initializer list in the same order as the members' declaration in the class, to match the actual initialization order and avoid compiler warnings.
3. Mark single-argument constructors explicit unless an implicit conversion from that argument type is genuinely intended.
4. Use delegating constructors to avoid duplicating initialization logic across overloaded constructors.
5. Reach for = default and = delete to state a class's intended constructor set explicitly, rather than relying only on the compiler's implicit rules or writing an empty body by hand.
6. Keep a destructor's job focused on releasing what the constructor acquired; a class that acquires no resources usually needs no user-written destructor at all.
Complete Example
Resource has a parameterized constructor and a default constructor that delegates to it, plus a destructor that reports cleanup.
first is constructed directly with label "database" second is default-constructed, which delegates to Resource("unnamed") the body prints a message using both labels at the end of main, second and first are destroyed in reverse order of construction
The program's output, showing construction order followed by reverse-order destruction.
#include <iostream>
#include <string>
class Resource
{
public:
Resource()
: Resource("unnamed")
{
}
explicit Resource(std::string label)
: label(label)
{
std::cout << "Acquired " << label << '\n';
}
~Resource()
{
std::cout << "Released " << label << '\n';
}
std::string label;
};
int main()
{
Resource first("database");
Resource second;
std::cout << "Working with " << first.label << " and " << second.label << '\n';
return 0;
}Acquired database
Acquired unnamed
Working with database and unnamed
Released unnamed
Released databaseMental Model
Think of a constructor and destructor as the two ends of a bracket around an object's usable lifetime.
Every object's lifetime is bracketed by exactly one constructor call and exactly one destructor call, and objects sharing a scope are torn down in the reverse of the order they were built.
Constructor runs -----> [ object exists and is usable ] -----> Destructor runs
Multiple objects in one scope (LIFO destruction):
Construct: first -> second -> third
Destruct: third -> second -> firstQuick Reference
A quick-reference summary of constructor and destructor syntax covered in this topic.
Concept | Example | Meaning
Default constructor | ClassName() { ... } | Callable with no arguments
Parameterized constructor | ClassName(int v) { ... } | Takes initial values
Initializer list | ClassName(int v) : x(v) {} | Initializes members directly
Delegating constructor | ClassName() : ClassName(0) {} | Calls another constructor
explicit | explicit ClassName(int v) | Blocks implicit conversion
Destructor | ~ClassName() { ... } | Runs automatically at end of lifetime
= default | ClassName() = default; | Requests the compiler-generated version
= delete | ClassName() = delete; | Forbids that member functionKey Takeaways
A constructor initializes a newly created object automatically; a destructor cleans it up automatically when its lifetime ends.
Member initializer lists initialize data members directly and are required for const and reference members; assignment in the constructor body initializes then overwrites.
Members are always initialized in declaration order, regardless of the order written in the initializer list.
explicit prevents a single-argument constructor from being used as an implicit conversion.
Objects sharing a scope are destroyed in the reverse of the order they were constructed.
= default requests the compiler's ordinary implementation explicitly; = delete forbids a member function from being used at all.
Tying resource release to a destructor is the foundation of RAII, covered in its own dedicated topic.
What is the practical difference between initializing a member in a constructor's initializer list versus assigning to it in the constructor body?
An initializer list initializes the member directly, using the given value as its initial value with no prior step. Assignment in the body means the member is first default-initialized (which may run a default constructor or leave a built-in type indeterminate) and is then overwritten by the assignment. For const members and reference members, only the initializer list works, because both must be initialized once and cannot be assigned to afterward.
Given a class where the initializer list is written as : b(bValue), a(aValue) but a is declared before b in the class body, which member is actually initialized first at runtime?
a is initialized first, because data members are always initialized in the order they are declared in the class, not the order they appear in the initializer list. The initializer list's written order (b before a) does not change this; a compiler will typically warn about the mismatch.
Why does marking a single-argument constructor explicit change what code compiles, and give an example of code that only compiles without explicit?
explicit disables the constructor's ability to perform an implicit conversion from its argument type to the class type. Without explicit, a constructor Meters(double) allows code like Meters m = 5.0; or a function call expecting a Meters to accept a plain double argument directly, silently constructing a temporary Meters. With explicit, both of those must instead write the conversion out, e.g. Meters m = Meters(5.0);; the implicit form fails to compile.
Three objects, a, b, and c, of a class with a printing constructor and destructor are declared in that order inside one function. In what order do their destructors run when the function returns, and why?
Their destructors run in the reverse order: c, then b, then a. Objects with automatic storage duration that share a scope are destroyed in the opposite order from which they were constructed — the object constructed last is the first one destroyed.
What is the difference between writing Widget() = default; and simply leaving Widget with no user-declared default constructor at all?
If a class has no user-declared constructors at all, the compiler implicitly generates an ordinary default constructor on its own. Writing Widget() = default; is needed specifically when the class already declares another constructor (which would otherwise suppress the implicit default constructor) but the class author still wants the ordinary compiler-generated default constructor to exist and be used.
Trace through this code. What is printed, and in what order are the destructors called relative to the constructors?
a is constructed first (printing 'Built 1'), then b (printing 'Built 2'). When run's scope ends, the objects are destroyed in reverse declaration order: b first ('Gone 2'), then a ('Gone 1'). Full output: Built 1, Built 2, Gone 2, Gone 1.
Write a class named Circle with a private double radius member, a constructor taking a double and initializing radius via a member initializer list, and a public member function area() returning 3.14159 * radius * radius. Mark the constructor explicit. Create a Circle with radius 2.0 and print its area.
The constructor initializes radius directly to 2.0 via the initializer list. area() computes 3.14159 * 2.0 * 2.0, which is 12.56636. The explicit keyword means Circle c = 2.0; would not compile, but Circle c(2.0); (direct initialization) is unaffected. The program prints 12.56636.
Trace through this code, which uses a delegating constructor. What does it print?
Pair()'s initializer list delegates to Pair(0, 0), which initializes a and b to 0 and 0. p1 is created this way, so p1.a is 0 and p1.b is 0. p2 is created directly with Pair(3, 4), giving p2.a as 3 and p2.b as 4. Output: 0 0 3 4.
Write a class named FixedId that has a public const int member id, a constructor taking an int and initializing id via the initializer list, and no default constructor (mark it = delete). Demonstrate creating a FixedId with id 42 and printing it; add a comment showing the line that would fail to compile if you tried to default-construct one.
id is const, so it must be initialized (never assigned) — the initializer list handles this correctly. The default constructor is explicitly deleted, so FixedId broken; would fail to compile with no matching constructor available; only FixedId(int) can be used. The program prints 42.
class Box
{
public:
explicit Box(int sideValue) : side(sideValue)
{
std::cout << "Built " << side << '\n';
}
~Box()
{
std::cout << "Gone " << side << '\n';
}
int side;
};
void run()
{
Box a(1);
Box b(2);
}
run();class Pair
{
public:
Pair() : Pair(0, 0) {}
Pair(int aValue, int bValue) : a(aValue), b(bValue) {}
int a;
int b;
};
Pair p1;
Pair p2(3, 4);
std::cout << p1.a << ' ' << p1.b << ' ' << p2.a << ' ' << p2.b;