C programmers have long acknowledged the challenge of using pointers. Creating reusable data-structure libraries further compounds the challenges. Void pointers can generalize the library functions but require the client to typecast the pointers to a "known" or defined type before using them. Furthermore, the client is responsible for initializing the pointers and deallocating the structure's heap memory. While the C libraries typically provide functions completing these tasks, application programmers can easily overlook calling them. C++'s template variables solve the generalization problem without requiring casts, and its constructors and destructors alleviate the initialization and deallocation problems. Nevertheless, pointer errors remain a persistent source of program bugs. Smart pointers address many of the remaining bugs.
Smart pointers wrap traditional or raw pointers with template classes, further reducing the complexity leading to their associated bugs. They provide an efficient kind of automatic garbage collection with minimal overhead. (Smart pointers still manage memory deallocation with destructors rather than running a program-wide mark and sweep algorithm.) However, automatic memory management is only one smart-pointer advantage: smart pointers make the wrapped raw pointer private
and only allow access through a limited public interface. Raw pointers are a frequent source of runtime errors. In conjunction with C++'s stronger type checking and smart pointer's controlled interface, the compiler can detect many errors before program execution, simplifying and hastening their detection and correction.
Programmers access the smart pointer system with the <memory> header file, which contains the smart pointer class specifications and function prototypes. Although the system is extensive, three smart pointer classes and a few of their member functions provide the fundamental and most frequently used features.
unique_ptr<T> | shared_ptr<T> | weak_ptr<T> | |
---|---|---|---|
A pointer exclusively responsible for deallocating a resource. | A pointer with shared responsibility for deallocating a resource. | A shared pointer that does not add to a pointer's reference count. | |
Create | make_unique<T>() | make_shared<T>() | |
lock() | • | ||
operator-> | • | • | |
operator* | • | • | |
operator bool() | • | • | |
operator== operator!= |
• | expired() | • |
use_count() | • | • | |
get() | • | • | |
reset() | • | • | • |
release() | • | ||
unique() | • |
![]() |
class part { private: string name; public: part(string n) : name(n) {} ~part() { cout << "dtor\n"; } }; |
int main() { shared_ptr<part> p1 = make_shared<part>("Widget"); shared_ptr<part> p2 = p1; weak_ptr<part> w = p1; cout << p1.use_count() << endl; return 0; } |
2 dtor
The text demonstrates the smart pointer syntax and behavior with small, simple programs. The examples all depend on the same library classes, implying that they require the same header files. Furthermore, each replaces the template variable with the same part class. So, for brevity, the following figure presents these common features.
#include <iostream>
#include <string>
#include <memory>
using namespace std;
class part
{
private:
string name;
int id;
public:
part(string n, int i) : name(n), id(i) {}
string get_name() { return name; }
};
void f1(unique_ptr<part>& p) // (a) { cout << p->get_name() << endl; } int main() { unique_ptr<part> unique // (b) = make_unique<part>("Widget", 10); // (c) cout << unique->get_name() << endl; // (d), prints '(1) Widget' f1(unique); // prints '(2) Widget' part* p = unique.release(); // (e) cout << p->get_name() << endl; // (f), prints '(3) Widget' if (unique) // (g) cout << unique->get_name() << endl; // No output else cout << "unique is empty\n"; // prints 'unique is empty' unique.reset(new part("Screw", 30)); // (h) if (unique) cout << unique->get_name() << endl; // prints '(4) Screw' else cout << "unique is empty\n"; // No output return 0; }
new
to create the part object.(unique)
invokes operator bool()
, returning true if the unique pointer is not empty, or false if it is.//void f2(shared_ptr<part>& p) // (a) void f2(shared_ptr<part> p) { cout << "(4) " << p->get_name() << " " << p.use_count() << endl; } int main() { shared_ptr<part> shared = // (b) make_shared<part>("Bolt", 20); // (c) shared_ptr<part> shared2 = shared; // (d) shared_ptr<part> shared3 = make_shared<part>("Bolt", 20); // (e) cout << "(1) " << shared->get_name() << " " << shared.use_count() << endl; // (f), prints '(1) Bolt 2' cout << "(2) " << shared2->get_name() << " " << shared2.use_count() << endl; // (f), prints '(2) Bolt 2' cout << "(3) " << shared3->get_name() << " " << shared3.use_count() << endl; // (g), prints '(3) Bolt 1' f2(shared); // (h), prints '(4) Bolt 3' if (shared.unique()) // (i) cout << "Unique\n"; // No output else cout << "Shared\n"; // prints 'Shared' shared.reset(new part("Screw", 30)); // (j) if (shared) // (k) cout << shared->get_name() << endl; // (l), prints 'Screw' else cout << "shared is empty\n"; // No output return 0; }
new
to create the part object.new
returns a distinct part object. Therefore, shared3 has a reference count of 1, and the counts for shared and shared2 are unchanged.(shared)
invokes operator bool()
, returning true if the shared pointer is not empty, or false if it is.void f3(shared_ptr<part> p1, weak_ptr<part> p2) // (a) { cout << "(3) " << p1.use_count() << " " << p2.use_count() << endl; // (b), prints '(3) 2 2' } int main() { shared_ptr<part> shared = make_shared<part>("Gadget", 40); weak_ptr<part> weak = shared; // (c) cout << "(1) " << shared->get_name() << " " << shared.use_count() << endl; // prints '(1) Gadget 1' cout << "(2) " << weak.use_count() << endl; // (d), prints '(2) 1' f3(shared, weak); shared_ptr<part> locked = weak.lock(); // (e) cout << "(4) " << shared.use_count() << " " << locked.use_count() // (f), prints '(4) 2 2 2' << " " << weak.use_count() << endl; weak.reset(); // (g) if (weak.expired()) // (h) cout << "weak unavailable" << endl; // prints else cout << weak.use_count() << endl; // no output cout << "(5) " << shared.use_count() << " " << locked.use_count() << endl; // (i), prints '(5) 2 2' locked.reset(); // (j) cout << "(6) " << shared.use_count() << " " << locked.use_count() << endl; // (k), prints '(6) 1 0' if (locked) // (l) cout << locked.use_count() << endl; // no output else cout << "locked unavailable" << endl; // prints cout << "(7) " << shared.use_count() << endl; // (m), prints '(7) 1' return 0; }
operator bool()
is false because lock is empty.View | Download | Comments |
---|---|---|
basic.cpp | basic.cpp | A simple smart pointer example, including the #include directives |
unique.cpp | unique.cpp | The complete unique smart pointer example |
shared.cpp | shared.cpp | The complete shared smart pointer example |
weak.cpp | weak.cpp | The complete weak smart pointer example |