Free tutorials & notes in Hindi & English · Clean code examples · Mobile friendly learning
🔴 Advanced  ·  Lesson 61

Smart Pointers

Written and reviewed by · 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 needPreferred representation
Ordinary scoped objectObject by value
Dynamic sequence/textvector/string
Exclusive dynamic ownerunique_ptr<T>
Genuinely shared lifetimeshared_ptr<T>
Non-owning observation of shared objectweak_ptr<T>
Ordinary non-owning parameterT&, const T& or documented T*
First question: can the object simply be a local value? Dynamic allocation and smart pointers are not automatically better than value semantics.

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 end

unique_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.

MemberPurpose
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
swapExchange owners
operator boolTest whether stored pointer is non-null
release is advanced: the caller becomes responsible for the returned resource. Calling it without immediately transferring ownership commonly leaks.

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 intentParameter/return
Use a required object, no ownershipconst T& or T&
Optional non-ownerDocumented T*
Take exclusive ownershipunique_ptr<T> by value
May replace caller's unique ownerunique_ptr<T>&
Create and return exclusive objectunique_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 point

A 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.

Never do this:
Widget* raw = new Widget;
std::shared_ptr<Widget> a(raw);
std::shared_ptr<Widget> b(raw); // separate control blocks: double deletion
Create 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();
}
Created: Aman Owns before move: true Enrolled: Aman Destroyed: Aman Owns after move: false Owners: 1 Observed: 42 Expired: true

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

MistakeCorrection
shared_ptr by defaultPrefer value or unique ownership
Copying unique_ptrTransfer with move only when intended
Two smart owners from one raw pointerCreate one owner and copy/move it
shared_ptr cycleMake non-owning back-links weak
Passing shared_ptr for ordinary accessPass reference/pointer view
Using get() then deleting raw pointerOwner performs deletion
Calling release without new ownerTransfer immediately or avoid release
Assuming pointee is thread-safeSynchronize 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.

  1. Could value semantics replace allocation?
  2. Is ownership exclusive or truly shared?
  3. Does the type express transfer?
  4. Are non-owners clearly bounded?
  5. Can a shared cycle form?
  6. Is the deleter correct and non-throwing?
  7. Is polymorphic destruction safe?
  8. Are threads synchronizing the object itself?

Authoritative References

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.

Ownership, RAII और Smart Pointers

Pointer location बताता है; ownership बताता है resource को alive और release कौन रखेगा। Smart pointer type में dynamic ownership express करता और RAII से scope/return/exception पर cleanup करता है।

NeedRepresentation
Scoped objectValue
Dynamic sequencevector/string
Exclusive ownerunique_ptr
Shared lifetimeshared_ptr
Shared object observerweak_ptr
Ordinary non-ownerReference/documented pointer
पहले पूछें: क्या local value पर्याप्त है? Dynamic allocation automatic improvement नहीं। new/delete और RAII पढ़ें।

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 देता है।

MemberUse
getRaw observation, ownership stays
* / ->Non-null object access
resetCurrent release, new own
releaseOwnership छोड़कर raw pointer
swapOwners exchange
boolNon-null test
release के returned pointer का new owner न बने तो leak होगा। unique_ptr<Base> से Derived destroy हो तो Base destructor virtual होना चाहिए।

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));
IntentType
Use onlyconst T&/T&
Optional non-ownerDocumented T*
Take ownershipunique_ptr by value
Reseat ownerunique_ptr&
Create ownerReturn 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 here

Control 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 blocks
One 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();
Created: Aman Owns before move: true Enrolled: Aman Destroyed: Aman Owns after move: false Owners: 1 Observed: 42 Expired: true

enroll parameter ने exclusive ownership लिया और scope end पर Student destroy हुआ। weak observer strong count नहीं बढ़ाता और sole owner reset पर expired हो गया।

Mistakes, Practice और Review

MistakeCorrection
shared_ptr defaultValue/unique prefer
unique_ptr copyIntentional move
One raw से two ownersOne owner copy/move
Shared cycleWeak back-link
Access के लिए shared paramReference/view
get pointer deleteOwner cleans
release then loseImmediate transfer
Pointee thread-safe assumeSeparate synchronization

Practice: Shape factory, shared Course, weak Parent–Child, FILE custom deleter और PImpl class बनाएं।

  1. Value possible?
  2. Exclusive/shared?
  3. Transfer visible?
  4. Borrow lifetime?
  5. Cycle?
  6. Deleter?
  7. Virtual destruction?
  8. Thread safety?

Authoritative संदर्भ

Ownership और API guidance current draft/Core Guidelines से verified है: default unique ownership और lifetime semantics में ही smart-pointer parameters।

अक्सर पूछे जाने वाले प्रश्न (FAQ)

C++ smart pointer क्या है?
Standard smart pointer RAII object है जो dynamically allocated object का ownership express करता और ownership खत्म होने पर correct cleanup चलाता है। Main types unique_ptr, shared_ptr और weak_ptr हैं।
unique_ptr और shared_ptr में क्या अंतर है?
unique_ptr exclusive move-only ownership देता है। shared_ptr control block से shared ownership देता है; last shared owner खत्म होने पर managed object release होता है। Sharing जरूरी न हो तो unique_ptr लें।
unique_ptr copy क्यों नहीं होता?
Copy से दो exclusive owners बन जाते। Ownership std::move से explicitly transfer होता है और source empty हो जाता है।
weak_ptr कौन-सी problem solve करता है?
weak_ptr strong ownership count बढ़ाए बिना shared object observe करता है। यह shared_ptr cycles तोड़ता और lock() से temporary safe access देता है।
क्या हर raw pointer को smart pointer बनाना चाहिए?
नहीं। Smart pointer ownership express करता है। Clear lifetime वाले non-owning views के लिए raw pointer/reference ठीक हैं; owner/transfer/share में owning type लें।
← Back to C++ Tutorial
🔗

Share this topic with a friend

यह topic किसी दोस्त को भेजें

Found it useful? Send it to a classmate learning the same thing.

अच्छा लगा? जो दोस्त यही सीख रहा है, उसे भेज दीजिए।