Constructors, Destructors, and RAII
When you create an object in C++, the language guarantees that a special member function called a constructor runs first, before any other code touches the object. When the object goes out of scope or is explicitly deleted, a destructor runs automatically. These two hooks — constructor and destructor — form the basis of a powerful C++ idiom called RAII (Resource Acquisition Is Initialization): acquire a resource in the constructor, release it in the destructor, and the language handles the rest for you automatically, even in the presence of exceptions.
Constructors
A constructor is a special member function with the same name as the class and no return type (not even void). It is called automatically when an object is created. Its job is to put the object into a valid initial state — initializing all member variables to meaningful values.
class BankAccount {
public:
// Constructor — same name as class, no return type
BankAccount(const std::string& ownerName, double initialBalance)
: owner(ownerName), balance(initialBalance) // member initializer list
{
// body — runs after member init list
if (balance < 0)
balance = 0; // enforce invariant
}
double getBalance() const { return balance; }
const std::string& getOwner() const { return owner; }
private:
std::string owner;
double balance;
};
BankAccount harrys("Harry", 1500.0); // constructor called here
std::cout << harrys.getOwner() << ": " << harrys.getBalance();The member initializer list (after the colon, before the body) initializes members directly — more efficient than assigning in the body, and the only way to initialize const members or reference members.
The default constructor
A default constructor takes no arguments and is called when you create an object without arguments. If you define no constructors at all, the compiler generates a default constructor for you (which default-initializes or leaves trivial members uninitialized). Once you define any constructor, the compiler no longer generates the default constructor automatically — you must write it explicitly if you need both.
class Counter {
public:
Counter() : count(0) {} // explicit default constructor
Counter(int start) : count(start) {} // parameterized constructor
void increment() { ++count; }
int get() const { return count; }
private:
int count;
};
Counter c1; // calls Counter() — count = 0
Counter c2(10); // calls Counter(int) — count = 10
Counter c3{5}; // also calls Counter(int) — brace formC++11 allows in-class member initializers: you can provide default values directly in the class definition, which reduces constructor boilerplate:
class Counter {
public:
Counter() = default; // explicitly request the compiler-generated default
Counter(int start) : count(start) {}
private:
int count = 0; // default member initializer — used if not set by ctor
};Destructors
A destructoris the complement of a constructor: it runs automatically when an object's lifetime ends. Its name is a tilde followed by the class name (~ClassName()), it takes no arguments, and it returns nothing. For objects on the stack, the destructor runs when the variable goes out of scope. For heap objects, it runs when delete is called.
#include <fstream>
#include <iostream>
class LogFile {
public:
LogFile(const std::string& path) : file(path) {
std::cout << "LogFile opened: " << path << "\n";
}
~LogFile() {
file.close(); // automatically closes the file
std::cout << "LogFile closed.\n";
}
void write(const std::string& msg) {
file << msg << "\n";
}
private:
std::ofstream file;
};
void doWork() {
LogFile log("output.txt"); // constructor runs — file opens
log.write("Hello");
log.write("World");
// function ends — log goes out of scope — destructor runs — file closes
}
// No need to manually close the file!RAII — Resource Acquisition Is Initialization
RAII is the pattern of tying a resource's lifetime to an object's lifetime. “Resource acquisition” happens in the constructor; “release” happens in the destructor. Because the destructor is guaranteed to run when the object goes out of scope — even if an exception is thrown — RAII makes resource management automatic and exception-safe.
This is why C++ programmers rarely use raw new/ delete. Instead, they use RAII wrappers:
std::unique_ptr<T>Heap memory → Destructor calls deletestd::shared_ptr<T>Shared heap memory → Destructor decrements refcount; deletes when zerostd::ifstream / std::ofstreamFile handle → Destructor closes the filestd::lock_guard<Mutex>Mutex lock → Destructor unlocks the mutexstd::vector<T>Dynamic array memory → Destructor frees the buffer#include <memory>
void processData()
{
// RAII memory — no delete needed
auto buffer = std::make_unique<int[]>(1024);
// use buffer...
buffer[0] = 42;
// buffer goes out of scope here — memory freed automatically
// even if an exception was thrown above
}
// Compare to the fragile manual approach:
void processDataManual()
{
int* buffer = new int[1024];
// ... use buffer ...
// if an exception is thrown, we NEVER reach delete:
delete[] buffer; // memory leak if anything above throws!
}Key rules to remember
Constructors initialize — put the object into a valid state before any other code uses it
Use the member initializer list (: member(value)) rather than assigning in the body — it is more efficient and required for const members and references.
Destructors clean up — they run automatically when the object's lifetime ends
You do not call the destructor explicitly. It runs when a stack object goes out of scope or when delete is called on a heap object.
If you define any constructor, the default constructor is no longer generated
Write Counter() = default; to explicitly request the compiler-generated default constructor alongside your custom ones.
RAII: acquire in the constructor, release in the destructor
This pattern makes resource management automatic and exception-safe. The standard library uses it everywhere: unique_ptr, fstream, lock_guard.
Prefer RAII wrappers over raw new/delete
std::unique_ptr, std::vector, and std::string all manage their memory automatically via RAII. Manual new/delete is error-prone and almost never necessary in modern C++.