RAII
An object from new must be deleted on every path out of a function. An early return can skip the delete:
#include <iostream>
void print_square(int x) {
int* square = new int{x * x};
if (*square > 100) {
return; // ✗
}
std::cout << *square << '\n';
delete square;
}
int main() {
print_square(12);
print_square(5); // 25
}With x = 12, the function leaves through return before it reaches delete. The pointer square is destroyed, the address is lost, and the number stays on the heap until the program ends—a leak. A run does not show it: the program prints 25 and exits as usual. A delete can be put before every return, but sooner or later one of them gets forgotten.
Resource owner
A destructor does this job more reliably: it runs when an object's lifetime ends. For a local object, that is any exit from its block, including return. The idiom in which an object acquires a resource in its constructor and releases it in its destructor is called RAII. A resource is anything that must be given back after use: heap memory, an open file.
The name stands for Resource Acquisition Is Initialization. The resource is acquired when the owner object is created and given back when that object is destroyed.
class IntBox {
public:
IntBox(int value) : ptr_(new int{value}) {}
~IntBox() { delete ptr_; }
int get() const { return *ptr_; }
private:
int* ptr_;
};
void print_square(int x) {
IntBox square(x * x);
if (square.get() > 100) {
return;
}
std::cout << square.get() << '\n';
}IntBox owns the number: it creates it in the constructor and deletes it in the destructor. The new print_square has no delete at all, and it does not leak either. At every exit from the function, the compiler inserts a call to ~IntBox by itself.
Standard library classes follow the same rule. std::vector and std::string allocate memory for their elements and free it in their destructors, so no delete is written for them.
Common mistake
A resource owner is copied:
int main() {
IntBox a(5);
IntBox b = a; // ✗
}The compiler builds this program without a warning. IntBox b = a; copies the object field by field, so b.ptr_ gets the same address as a.ptr_. At the end of main, both destructors delete the same number, and a second delete is undefined behavior.
The copy is made by the copy constructor, and a resource owner either forbids it or copies the resource itself, like std::vector. The simplest choice is to forbid the copy by adding two lines with = delete to the public part of the class:
IntBox(const IntBox&) = delete;
IntBox& operator=(const IntBox&) = delete;The first line forbids a copy at creation, IntBox b = a;, and the second forbids the assignment b = a;. Now the compiler finds the mistake:
error: call to deleted constructor of 'IntBox'Such an object can still be passed to a function by reference, which does not copy it.