std::unique_ptr wraps sole ownership of a heap object — it cannot be copied, only moved, which structurally guarantees exactly one owner exists and therefore exactly one delete ever happens. std::shared_ptr instead uses atomic reference counting, allowing multiple owners; the underlying object is deleted only when the last shared_ptr referencing it is destroyed. std::make_unique and std::make_shared are the preferred way to create either, avoiding a naked new entirely. Modern C++ guidance is direct: default to unique_ptr, and reach for shared_ptr only when genuine shared ownership is actually needed.
unique_ptr: sole ownership, zero overhead
The object is automatically deleted when p goes out of scope — no explicit delete anywhere, and the copy line below genuinely won't compile, which is exactly the guarantee that prevents a double-free.
struct Person {
std::string name;
Person(std::string n) : name(std::move(n)) {}
void greet() { std::cout << "Hi, I'm " << name << "\n"; }
};
std::unique_ptr<Person> p = std::make_unique<Person>("Alice");
p->greet();
// no delete needed — happens automatically when p goes out of scope
// std::unique_ptr<Person> p2 = p; // COMPILE ERROR: unique_ptr cannot be copied
std::unique_ptr<Person> p2 = std::move(p); // MOVING is fine — ownership transfers, p is now emptyshared_ptr: reference-counted shared ownership
The object is deleted only when the LAST shared_ptr referencing it is destroyed — use_count() shows exactly how many owners currently exist.
struct Person {
std::string name;
Person(std::string n) : name(std::move(n)) {}
};
std::shared_ptr<Person> p1 = std::make_shared<Person>("Bob");
std::cout << p1.use_count() << "\n"; // 1
std::shared_ptr<Person> p2 = p1; // COPYING is fine — both now share ownership
std::cout << p1.use_count() << "\n"; // 2
p1.reset(); // p1 gives up its ownership
std::cout << p2.use_count() << "\n"; // 1 — object still alive, p2 still owns it
// object is deleted only when p2 ALSO goes out of scope or is resetWhy raw new/delete is discouraged in modern C++
make_unique and make_shared avoid an explicit new entirely, and the resulting object's lifetime is tied structurally to the smart pointer's scope — there's no code path (including an early return or an exception) that can accidentally skip the cleanup, which was always the real risk with manual new/delete pairing.