Fundamentals 24 min read

Smart Pointers in C++: Problems Solved, Usage, Best Practices, and Implementation Details

C++11 smart pointers—unique_ptr, shared_ptr, and weak_ptr—replace manual new/delete by providing exclusive, shared, and non‑owning ownership semantics, automatically preventing leaks and dangling references, simplifying multithreaded code, and, when used with best‑practice guidelines, avoid common pitfalls such as double deletions and cyclic dependencies.

Tencent Cloud Developer
Tencent Cloud Developer
Tencent Cloud Developer
Smart Pointers in C++: Problems Solved, Usage, Best Practices, and Implementation Details

Smart pointers were introduced in the C++11 standard to replace the problematic auto_ptr . They help manage heap‑allocated objects, prevent memory leaks, and simplify multithreaded programming.

Why Use Smart Pointers

C++ requires manual new and delete . In complex or multithreaded code it is easy to forget a delete , leading to leaks. The following example shows a leak when an early return skips the deletion:

void test_memory_leak(bool open) {
    A *a = new A();
    if(open) {
        // code becomes complex, delete(a) may be omitted
        return;
    }
    delete(a);
    return;
}

Multithreaded Object Destruction

Using raw pointers in a singleton can cause a dangling pointer when a worker thread outlives the object. The ReportClass example demonstrates the issue and a solution that uses shared_ptr for the singleton and passes a weak_ptr to the worker thread.

class ReportClass {
private:
    std::mutex mutex_;
    int count_ = 0;
    void addWorkThread();
public:
    static ReportClass* GetInstance();
    static void ReleaseInstance();
    void pushEvent(std::string event);
};

std::mutex ReportClass::static_mutex_;
ReportClass* ReportClass::instance_ = nullptr;

ReportClass* ReportClass::GetInstance() {
    std::lock_guard
lock(static_mutex_);
    if (!instance_) {
        instance_ = new ReportClass();
        instance_->addWorkThread();
    }
    return instance_;
}

void ReportClass::addWorkThread() {
    std::thread work_thread(workThread, this);
    work_thread.detach();
}

void ReportClass::pushEvent(std::string event) {
    std::unique_lock
lock(mutex_);
    ++count_;
}

Basic Usage of Smart Pointers

unique_ptr provides exclusive ownership. It cannot be copied, only moved.

class A { public: void do_something() {} };

void test_unique_ptr(bool open) {
    std::unique_ptr
a(new A());
    a->do_something();
    if (open) {
        // no manual delete needed
        return;
    }
    // no manual delete needed
    return;
}

// transfer of ownership
std::unique_ptr
a1(new A());
// std::unique_ptr
a2 = a1; // compile error – copy not allowed
std::unique_ptr
a3 = std::move(a1); // ownership transferred, a1 becomes empty

Key unique_ptr members:

get() – returns the raw pointer (use sparingly).

Conversion to bool – checks if it holds a pointer.

release() – releases ownership without deleting.

reset() – replaces the managed object.

shared_ptr provides shared ownership via reference counting.

std::shared_ptr
a1(new A());
std::shared_ptr
a2 = a1; // reference count becomes 2

if (a1.unique()) {
    // count == 1
}
long cnt = a1.use_count(); // current reference count

Important shared_ptr members:

get() – raw pointer (avoid if possible).

Conversion to bool – checks non‑null.

reset() – releases current object and optionally takes a new one.

unique() – true when reference count is 1.

use_count() – returns the reference count.

weak_ptr observes a shared_ptr without affecting its lifetime, breaking cyclic references.

std::shared_ptr
a1(new A());
std::weak_ptr
weak_a1 = a1; // does not increase ref count

if (weak_a1.expired()) {
    // the object has been destroyed
}
if (std::shared_ptr
sp = weak_a1.lock()) {
    // safe to use sp
}
weak_a1.reset(); // clear the weak pointer

Best Practices

Prefer unique_ptr for exclusive ownership – it has no reference‑count overhead.

Use shared_ptr only when multiple owners are required.

Prefer std::make_shared<T>() over new to avoid a second allocation.

Avoid mixing raw pointers with smart pointers for the same object.

Never delete a raw pointer that is already managed by a smart pointer.

Never construct multiple smart pointers from the same raw pointer.

Avoid calling get() unless you absolutely need the raw pointer.

Never manage stack‑allocated objects with smart pointers.

When a class needs to hand out a shared_ptr to itself, inherit from std::enable_shared_from_this and use shared_from_this() instead of constructing a shared_ptr from this .

Common Mistakes

Examples that lead to double deletion, dangling pointers, or memory leaks:

void incorrect_smart_pointer1() {
    A *a = new A();
    std::unique_ptr
up(a);
    delete a; // double free!
}

void incorrect_smart_pointer2() {
    A *a = new A();
    std::unique_ptr
up1(a);
    std::unique_ptr
up2(a); // double free on destruction
}

void incorrect_smart_pointer3() {
    std::shared_ptr
sp1 = std::make_shared
();
    A *a = sp1.get();
    std::shared_ptr
sp2(a); // double free
    delete a; // double free again
}

class E {
    void use_this() {
        std::shared_ptr
this_sp(this); // undefined behavior – double delete
    }
};

Implementation Details (libc++)

The internal representation of unique_ptr consists of a compressed pair holding the raw pointer and the deleter. Its destructor simply calls reset() which invokes the deleter if the pointer is non‑null.

template
>
class unique_ptr {
public:
    using pointer = _Tp*;
    constexpr unique_ptr() noexcept : __ptr_(pointer()) {}
    explicit unique_ptr(pointer p) noexcept : __ptr_(p) {}
    ~unique_ptr() { reset(); }
    pointer get() const noexcept { return __ptr_.first(); }
    pointer release() noexcept { pointer tmp = __ptr_.first(); __ptr_.first() = pointer(); return tmp; }
    void reset(pointer p = pointer()) noexcept { pointer old = __ptr_.first(); __ptr_.first() = p; if (old) __ptr_.second()(old); }
    // move operations, operator*, operator-> omitted for brevity
private:
    __compressed_pair
__ptr_;
};

shared_ptr stores a raw pointer and a pointer to a control block ( __shared_weak_count ) that holds the reference counters. Construction from a raw pointer creates a __shared_ptr_pointer control block; copy construction increments the shared counter; destruction decrements it and destroys the managed object when the count reaches zero.

template
class shared_ptr {
public:
    explicit shared_ptr(_Tp* p) : __ptr_(p) {
        __cntrl_ = new __shared_ptr_pointer<_Tp*, default_delete<_Tp>>(p, default_delete<_Tp>(), allocator_type());
    }
    shared_ptr(const shared_ptr& r) noexcept : __ptr_(r.__ptr_), __cntrl_(r.__cntrl_) { if (__cntrl_) __cntrl_->__add_shared(); }
    ~shared_ptr() { if (__cntrl_) __cntrl_->__release_shared(); }
    // get, use_count, reset, etc. omitted for brevity
private:
    _Tp* __ptr_;
    __shared_weak_count* __cntrl_;
};

weak_ptr holds the same control block but does not affect the shared count. It can be upgraded to a shared_ptr via lock() , which returns an empty shared_ptr if the object has already been destroyed.

template
class weak_ptr {
public:
    weak_ptr() noexcept : __ptr_(nullptr), __cntrl_(nullptr) {}
    weak_ptr(const shared_ptr<_Tp>& r) noexcept : __ptr_(r.__ptr_), __cntrl_(r.__cntrl_) { if (__cntrl_) __cntrl_->__add_weak(); }
    ~weak_ptr() { if (__cntrl_) __cntrl_->__release_weak(); }
    std::shared_ptr<_Tp> lock() const noexcept {
        if (__cntrl_ && __cntrl_->use_count() > 0) {
            __cntrl_->__add_shared();
            return std::shared_ptr<_Tp>(__ptr_, __cntrl_);
        }
        return std::shared_ptr<_Tp>();
    }
private:
    _Tp* __ptr_;
    __shared_weak_count* __cntrl_;
};

Conclusion

Smart pointers are indispensable for safe memory management in modern C++. Understanding their ownership semantics, proper usage patterns, and common pitfalls enables developers to write robust, leak‑free, and thread‑safe code.

Memory ManagementCMultithreadingshared_ptrsmart pointersunique_ptrweak_ptr
Tencent Cloud Developer
Written by

Tencent Cloud Developer

Official Tencent Cloud community account that brings together developers, shares practical tech insights, and fosters an influential tech exchange community.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.