Smart Pointers
Written and reviewed by Gagan Bhardwaj · Senior IT Faculty · 15+ years’ experience
Ownership, RAII and Why Smart Pointers Exist
A pointer answers where an object is; ownership answers who must keep it alive and release its resource. Smart pointers encode dynamic ownership in a type and use RAII: cleanup runs in the owner's destructor during normal return, early return and exception unwinding.
| Lifetime need | Preferred representation |
|---|---|
| Ordinary scoped object | Object by value |
| Dynamic sequence/text | vector/string |
| Exclusive dynamic owner | unique_ptr<T> |
| Genuinely shared lifetime | shared_ptr<T> |
| Non-owning observation of shared object | weak_ptr<T> |
| Ordinary non-owning parameter | T&, const T& or documented T* |
Read new/delete and RAII for the storage/lifetime foundation.
unique_ptr: Exclusive Ownership
#include <memory>
auto score = std::make_unique<int>(95);
std::cout << *score;
score.reset(); // releases now; automatic release would occur at scope endunique_ptr has strict ownership semantics: it is movable but not copyable. Its default deleter uses the appropriate delete form for its specialization. Prefer make_unique because it is concise and acquires directly into an owner.
| Member | Purpose |
|---|---|
get() | Observe stored pointer; ownership stays |
operator*/-> | Access non-null managed object |
reset(p) | Release current object, then own p |
release() | Return pointer and abandon ownership without deleting |
swap | Exchange owners |
operator bool | Test whether stored pointer is non-null |
A unique_ptr<Base> may own a Derived object. If destruction occurs through Base, Base must have an appropriate virtual destructor.
Moving Ownership and Designing APIs
std::unique_ptr<Student> make_student(std::string name) {
return std::make_unique<Student>(std::move(name));
}
void enroll(std::unique_ptr<Student> student) {
// takes ownership; automatic cleanup unless moved onward
}
auto learner = make_student("Aman");
enroll(std::move(learner));
// learner is now empty| Function intent | Parameter/return |
|---|---|
| Use a required object, no ownership | const T& or T& |
| Optional non-owner | Documented T* |
| Take exclusive ownership | unique_ptr<T> by value |
| May replace caller's unique owner | unique_ptr<T>& |
| Create and return exclusive object | unique_ptr<T> |
Do not accept a smart pointer when the function merely calls methods on the object. That unnecessarily restricts callers and hides the real contract. Pass an owning type only to express ownership semantics.
std::move does not move an object by itself; it enables the move operation selected by the receiving expression. Check or reassign a moved-from unique_ptr before dereferencing.
shared_ptr and Its Control Block
auto course = std::make_shared<Course>("C++");
auto second_owner = course;
std::cout << course.use_count(); // 2 in this single-threaded pointA shared_ptr stores a pointer and participates in a control block that tracks shared ownership and weak observations. When the last shared owner ends, the managed object is destroyed. The control block remains while weak observers still exist.
Prefer make_shared for ordinary shared objects: construction is concise and an implementation can combine object/control-block allocation. Use direct construction with a custom deleter or allocation requirement when necessary.
Widget* raw = new Widget;
std::shared_ptr<Widget> a(raw);
std::shared_ptr<Widget> b(raw); // separate control blocks: double deletionCreate one owner and copy that shared_ptr.use_count() is diagnostic, not a synchronization or business-logic mechanism. Different shared_ptr objects can safely update their shared control block, but that does not make the pointed-to object thread-safe. Concurrent access to the same shared_ptr object may also require synchronization or atomic<shared_ptr>.
Use enable_shared_from_this when an already shared-owned object must safely obtain another shared owner to itself; never construct a new shared_ptr directly from this.
weak_ptr, lock() and Shared-Ownership Cycles
std::shared_ptr<int> owner = std::make_shared<int>(42);
std::weak_ptr<int> observer = owner;
if (auto temporary = observer.lock()) {
std::cout << *temporary; // temporary shared ownership
}
owner.reset();
if (observer.expired()) {
std::cout << "Object ended";
}weak_ptr does not keep the object alive. lock() returns an empty shared_ptr if ownership has ended, otherwise a temporary shared owner. Prefer lock() to checking expired() and then separately accessing; the lifetime could change between separate operations.
Breaking a cycle
struct Child;
struct Parent {
std::shared_ptr<Child> child;
};
struct Child {
std::weak_ptr<Parent> parent; // observer, breaks cycle
};If both links were shared_ptr, each object could keep the other alive after all external owners disappear. Model one direction as ownership and the back-link as observation.
Custom Deleters, Arrays and Non-memory Resources
Smart pointers can manage resources beyond objects when a correct deleter is supplied:
#include <cstdio>
#include <memory>
using File = std::unique_ptr<std::FILE, decltype(&std::fclose)>;
File file(std::fopen("report.txt", "w"), &std::fclose);
if (!file) {
// handle open failure
}The deleter is part of a unique_ptr's type. For shared_ptr, the deleter is stored in the control block. Deleters must correctly match the resource and should not throw during cleanup.
Arrays
auto data = std::make_unique<int[]>(100);
data[0] = 7;The array specialization uses array deletion and provides indexing. For ordinary dynamic sequences, prefer std::vector because it tracks size and supplies algorithms, iterators and rich operations.
Incomplete types
unique_ptr supports the PImpl pattern, but the pointed type must be complete where the default deleter is instantiated for destruction. Define the owning class destructor out of line where the implementation type is complete.
Complete Ownership Lab with Verified Output
#include <iostream>
#include <memory>
#include <string>
#include <utility>
class Student {
public:
explicit Student(std::string name) : name_(std::move(name)) {
std::cout << "Created: " << name_ << '\n';
}
~Student() { std::cout << "Destroyed: " << name_ << '\n'; }
const std::string& name() const { return name_; }
private:
std::string name_;
};
void enroll(std::unique_ptr<Student> student) {
std::cout << "Enrolled: " << student->name() << '\n';
}
int main() {
std::cout << std::boolalpha;
auto learner = std::make_unique<Student>("Aman");
std::cout << "Owns before move: " << static_cast<bool>(learner) << '\n';
enroll(std::move(learner));
std::cout << "Owns after move: " << static_cast<bool>(learner) << '\n';
auto score = std::make_shared<int>(42);
std::weak_ptr<int> observer = score;
std::cout << "Owners: " << score.use_count() << '\n';
if (auto locked = observer.lock()) {
std::cout << "Observed: " << *locked << '\n';
}
score.reset();
std::cout << "Expired: " << observer.expired();
}The by-value enroll parameter receives exclusive ownership. Student is destroyed when that parameter leaves scope. The weak observer does not increase the strong count and becomes expired after the only shared owner resets.
Common Mistakes, Practice and Review
| Mistake | Correction |
|---|---|
| shared_ptr by default | Prefer value or unique ownership |
| Copying unique_ptr | Transfer with move only when intended |
| Two smart owners from one raw pointer | Create one owner and copy/move it |
| shared_ptr cycle | Make non-owning back-links weak |
| Passing shared_ptr for ordinary access | Pass reference/pointer view |
Using get() then deleting raw pointer | Owner performs deletion |
| Calling release without new owner | Transfer immediately or avoid release |
| Assuming pointee is thread-safe | Synchronize object access separately |
Practice: build a unique-owned polymorphic Shape factory, a shared course resource, a Parent–Child graph with weak back-link, a FILE owner with custom deleter, and a PImpl class. Run AddressSanitizer/UndefinedBehaviorSanitizer during development.
- Could value semantics replace allocation?
- Is ownership exclusive or truly shared?
- Does the type express transfer?
- Are non-owners clearly bounded?
- Can a shared cycle form?
- Is the deleter correct and non-throwing?
- Is polymorphic destruction safe?
- Are threads synchronizing the object itself?
Authoritative References
- C++ Working Draft: unique_ptr
- C++ Working Draft: shared_ptr
- C++ Working Draft: weak_ptr
- C++ Core Guidelines: Smart Pointer Ownership
Ownership, move, shared-control and weak-observation rules were checked against the current draft. Design advice follows the Core Guidelines: use unique ownership by default and smart-pointer parameters only when lifetime semantics require them.
Frequently Asked Questions
What is a smart pointer in C++?
What is the difference between unique_ptr and shared_ptr?
Why can a unique_ptr not be copied?
What problem does weak_ptr solve?
Should every raw pointer be replaced by a smart pointer?
Ownership, RAII और Smart Pointers
Pointer location बताता है; ownership बताता है resource को alive और release कौन रखेगा। Smart pointer type में dynamic ownership express करता और RAII से scope/return/exception पर cleanup करता है।
| Need | Representation |
|---|---|
| Scoped object | Value |
| Dynamic sequence | vector/string |
| Exclusive owner | unique_ptr |
| Shared lifetime | shared_ptr |
| Shared object observer | weak_ptr |
| Ordinary non-owner | Reference/documented pointer |
unique_ptr: Exclusive Ownership
auto score=std::make_unique<int>(95);
std::cout<<*score;
score.reset();unique_ptr moveable लेकिन copyable नहीं। make_unique concise direct ownership देता है।
| Member | Use |
|---|---|
| get | Raw observation, ownership stays |
| * / -> | Non-null object access |
| reset | Current release, new own |
| release | Ownership छोड़कर raw pointer |
| swap | Owners exchange |
| bool | Non-null test |
Move और API Design
std::unique_ptr<Student> make_student(std::string n){
return std::make_unique<Student>(std::move(n));
}
void enroll(std::unique_ptr<Student> student){ }
auto learner=make_student("Aman");
enroll(std::move(learner));| Intent | Type |
|---|---|
| Use only | const T&/T& |
| Optional non-owner | Documented T* |
| Take ownership | unique_ptr by value |
| Reseat owner | unique_ptr& |
| Create owner | Return unique_ptr |
Function केवल object use करता है तो smart pointer parameter न लें। Move के बाद unique_ptr empty है; check/reassign के बिना dereference न करें।
shared_ptr और Control Block
auto course=std::make_shared<Course>("C++");
auto second_owner=course;
std::cout<<course.use_count(); // 2 hereControl block shared owners और weak observers track करता है। Last shared owner पर object destroy होता; weak observers रहने तक control block रह सकता है। Ordinary construction में make_shared prefer करें।
Widget* raw=new Widget;
std::shared_ptr<Widget> a(raw);
std::shared_ptr<Widget> b(raw); // गलत: two control blocksOne owner बनाकर उसी shared_ptr को copy करें।use_count diagnostic है, synchronization rule नहीं। Control block safety pointed object को thread-safe नहीं बनाती। this से नया shared_ptr न बनाएं; जरूरत में enable_shared_from_this लें।
weak_ptr, lock और Cycles
auto owner=std::make_shared<int>(42);
std::weak_ptr<int> observer=owner;
if(auto temporary=observer.lock()) std::cout<<*temporary;
owner.reset();
if(observer.expired()) std::cout<<"Object ended";weak_ptr object alive नहीं रखता। lock empty या temporary shared owner देता है। expired check के बाद अलग access की जगह lock करें।
struct Child;
struct Parent{std::shared_ptr<Child> child;};
struct Child{std::weak_ptr<Parent> parent;};दोनों directions shared हों तो cycle cleanup रोक सकती है; back-link weak रखें।
Custom Deleters और Arrays
using File=std::unique_ptr<std::FILE,decltype(&std::fclose)>;
File file(std::fopen("report.txt","w"),&std::fclose);
if(!file){ /* failure */ }
auto data=std::make_unique<int[]>(100);
data[0]=7;Deleter resource से match और non-throwing हो। Unique_ptr array specialization delete[]/indexing देता है, लेकिन ordinary sequence में size/iterators के कारण vector बेहतर है। PImpl में owner destructor उस जगह define करें जहां implementation type complete हो।
Complete Ownership Lab और Output
auto learner=std::make_unique<Student>("Aman");
std::cout<<"Owns before move: "<<bool(learner)<<'\n';
enroll(std::move(learner));
std::cout<<"Owns after move: "<<bool(learner)<<'\n';
auto score=std::make_shared<int>(42);
std::weak_ptr<int> observer=score;
if(auto locked=observer.lock()) std::cout<<*locked;
score.reset();enroll parameter ने exclusive ownership लिया और scope end पर Student destroy हुआ। weak observer strong count नहीं बढ़ाता और sole owner reset पर expired हो गया।
Mistakes, Practice और Review
| Mistake | Correction |
|---|---|
| shared_ptr default | Value/unique prefer |
| unique_ptr copy | Intentional move |
| One raw से two owners | One owner copy/move |
| Shared cycle | Weak back-link |
| Access के लिए shared param | Reference/view |
| get pointer delete | Owner cleans |
| release then lose | Immediate transfer |
| Pointee thread-safe assume | Separate synchronization |
Practice: Shape factory, shared Course, weak Parent–Child, FILE custom deleter और PImpl class बनाएं।
- Value possible?
- Exclusive/shared?
- Transfer visible?
- Borrow lifetime?
- Cycle?
- Deleter?
- Virtual destruction?
- Thread safety?
Authoritative संदर्भ
Ownership और API guidance current draft/Core Guidelines से verified है: default unique ownership और lifetime semantics में ही smart-pointer parameters।