Chapter 24: Smart Pointers

Introduction

Smart pointers are classes that manage dynamically allocated memory and ensure proper cleanup when the memory is no longer needed. They provide a safer and more convenient way to manage memory than raw pointers.

Types of Smart Pointers

Move Semantics

Move semantics allow a class to transfer ownership of an object rather than making a copy.

Example

template<typename T>
class Auto_ptr4
{
    T* m_ptr {};
public:
    Auto_ptr4(T* ptr = nullptr) : m_ptr { ptr } { }

    ~Auto_ptr4() { delete m_ptr; }

    // Copy constructor
    Auto_ptr4(const Auto_ptr4& a)
    {
        m_ptr = new T;
        *m_ptr = *a.m_ptr;
    }

    // Move constructor
    Auto_ptr4(Auto_ptr4&& a) noexcept : m_ptr(a.m_ptr)
    {
        a.m_ptr = nullptr;
    }

    // Copy assignment
    Auto_ptr4& operator=(const Auto_ptr4& a)
    {
        if (&a == this)
            return *this;

        delete m_ptr;
        m_ptr = new T;
        *m_ptr = *a.m_ptr;
        return *this;
    }

    // Move assignment
    Auto_ptr4& operator=(Auto_ptr4&& a) noexcept
    {
        if (&a == this)
            return *this;

        delete m_ptr;
        m_ptr = a.m_ptr;
        a.m_ptr = nullptr;
        return *this;
    }

    T& operator*() const { return *m_ptr; }
    T* operator->() const { return m_ptr; }
    bool isNull() const { return m_ptr == nullptr; }
};

class Resource
{
public:
    Resource() { std::cout << "Resource acquired\n"; }
    ~Resource() { std::cout << "Resource destroyed\n"; }
};

Auto_ptr4<Resource> generateResource()
{
    return res; // this return value will invoke the move constructor
}

int main()
{
    Auto_ptr4<Resource> mainres;
    mainres = generateResource(); // this assignment will invoke the move assignment
    return 0;
}

std::unique_ptr

std::unique_ptr manages a unique resource. It should be used to manage any dynamically allocated object that is not shared by multiple objects.

Example

#include <memory> // for std::unique_ptr

class Resource
{
public:
    Resource() { std::cout << "Resource acquired\n"; }
    ~Resource() { std::cout << "Resource destroyed\n"; }
};

// allocate a Resource object and have it owned by std::unique_ptr
std::unique_ptr<Resource> res{ new Resource() };

// Create a dynamically allocated array of Fractions of length 4
auto f2{ std::make_unique<Fraction[]>(4) };

void takeOwnership(std::unique_ptr<Resource> res)
{
    if (res) // use implicit cast to bool to ensure res contains a Resource
    {
        // do something with res
    }
}

int main()
{
    std::unique_ptr<Resource> ptr{ new Resource() };
    takeOwnership(std::move(ptr)); // use move semantics
    return 0;
}

Guidelines for Using std::unique_ptr

std::shared_ptr

std::shared_ptr manages a shared resource using reference counting. The resource will be deallocated when the last std::shared_ptr managing the resource is destroyed.

Example

#include <memory> // for std::shared_ptr

class Resource
{
public:
    Resource() { std::cout << "Resource acquired\n"; }
    ~Resource() { std::cout << "Resource destroyed\n"; }
};

int main()
{
    auto ptr1 { std::make_shared<Resource>() };
    auto ptr2 { ptr1 }; // create ptr2 using copy of ptr1
    return 0;
}

std::weak_ptr

A std::weak_ptr is an observer -- it can observe and access the same object as a std::shared_ptr (or other std::weak_ptrs) but it is not considered an owner.

Example

#include <memory>
#include <iostream>

class Resource
{
public:
    Resource() { std::cout << "Resource acquired\n"; }
    ~Resource() { std::cout << "Resource destroyed\n"; }
};

std::weak_ptr<Resource> m_partner;

int main()
{
    auto ptr1 { std::make_shared<Resource>() };
    m_partner = ptr1; // weak_ptr observes the shared_ptr

    if (auto shared = m_partner.lock()) // use lock() to convert weak_ptr to shared_ptr
    {
        std::cout << "Resource is still alive\n";
    }
    else
    {
        std::cout << "Resource has been destroyed\n";
    }

    return 0;
}

Conclusion

Smart pointers provide a safer and more convenient way to manage dynamically allocated memory. Understanding how to use std::unique_ptr, std::shared_ptr, and std::weak_ptr is crucial for writing robust and maintainable C++ code.