Raw pointers require explicit deallocation before a program ends, otherwise memory leaks occur. Manual free calls are tedious and error-prone. C++11 introduced smart pointers to automate this process. The primary types are std::shared_ptr, std::unique_ptr, and std::weak_ptr, all defined in <memory>.
The Reference Counting Model
Reference counting prevents leaks by tracking the number of active references to a dynamically allocated object. Acquiring a new reference increments the counter, and releasing one decrements it. When the counter hits zero, the associated heap memory is automatically released.
Shared Ownership via std::shared_ptr
std::shared_ptr manages an object through shared ownership. It maintains a reference count reflecting how many shared_ptr instances point to the same object, eliminating manual delete. To avoid raw new, prefer std::make_shared.
#include <iostream>
#include <memory>
void addOne(std::shared_ptr<int> val) {
(*val)++;
}
int main() {
auto sp = std::make_shared<int>(20);
addOne(sp);
std::cout << *sp << std::endl; // prints 21
}
Member functions provide additional control: get() yields the raw pointer, use_count() reports the current reference count, and reset() decrements the count and detaches the shared_ptr.
#include <iostream>
#include <memory>
int main() {
auto p1 = std::make_shared<int>(50);
auto p2(p1);
auto p3 = p1;
std::cout << "p1: " << p1.use_count() << ", p2: " << p2.use_count()
<< ", p3: " << p3.use_count() << std::endl;
p3.reset();
std::cout << "After p3.reset() -> p1: " << p1.use_count()
<< ", p2: " << p2.use_count()
<< ", p3: " << p3.use_count() << std::endl;
p2.reset();
std::cout << "After p2.reset() -> p1: " << p1.use_count()
<< ", p2: " << p2.use_count()
<< ", p3: " << p3.use_count() << std::endl;
}
Exclusive Ownership with std::unique_ptr
std::unique_ptr enforces exclusive ownership. It forbids copying to other smart pointers, safeguarding against unintended sharing. Use std::make_unique for construction.
#include <iostream>
#include <memory>
int main() {
std::unique_ptr<int> up1 = std::make_unique<int>(100);
// std::unique_ptr<int> up2 = up1; // compiler error
std::unique_ptr<int> up2 = std::move(up1); // transfers ownership
std::cout << *up2 << std::endl;
}
Although copying is disallowed, ownership can be transferred via std::move. After the move, the source pointer becomes null and the destination assumes sole responsibility.
#include <iostream>
#include <memory>
class Demo {
public:
Demo() { std::cout << "Demo constructed\n"; }
~Demo() { std::cout << "Demo destroyed\n"; }
void greet() const { std::cout << "Hello from Demo\n"; }
};
void show(const Demo& obj) {
std::cout << "Inside show()\n";
}
int main() {
std::unique_ptr<Demo> a = std::make_unique<Demo>();
if (a) a->greet();
{
std::unique_ptr<Demo> b(std::move(a));
show(*b);
if (b) b->greet();
if (a) a->greet(); // a is null, so this is skipped
a = std::move(b); // return ownership to a
if (b) b->greet(); // b is now null
std::cout << "b going out of scope\n";
}
if (a) a->greet(); // a still owns the object
}
Breaking Cycles with std::weak_ptr
While shared_ptr solves many leak scenarios, cyclic dependencies can prevent deallocation. For example, two objects holding shared_ptr to each other never reach a zero reference count. std::weak_ptr is a non‑owning observer: it references an object managed by a shared_ptr without increasing the reference count, allowing cycles to be broken.
A weak_ptr lacks operator* and operator->. To acces the managed object, use lock(), which returns a valid shared_ptr if the object still exists, or a null pointer otherwise.
#include <iostream>
#include <memory>
struct Node;
struct Node {
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev; // weak reference to avoid cycle
};
int main() {
auto first = std::make_shared<Node>();
auto second = std::make_shared<Node>();
first->next = second;
second->prev = first; // uses weak_ptr
// Both shared_ptrs will be correctly destroyed when
// first and second go out of scope.
}
weak_ptr is typically used alongside shared_ptr to inspect the validity of a resource without extending its lifetime.