Smart Pointers in C++

Smart pointers are a feature introduced in C++11 to manage dynamic memory allocation more efficiently and safely than raw pointers. They are called “smart” because they not only act like pointers but also manage the memory they point to automatically. Smart pointers ensure proper memory deallocation, preventing memory leaks and dangling pointers, which are common pitfalls with raw pointers.

There are three types of smart pointers provided by the C++ Standard Library: std::unique_ptr, std::shared_ptr, and std::weak_ptr.

  1. std::unique_ptr:
    • std::unique_ptr is a smart pointer that owns the memory it points to.
    • It ensures that there is only one std::unique_ptr instance pointing to the allocated memory.
    • When the std::unique_ptr goes out of scope or is explicitly deleted, it automatically deallocates the memory it owns.
    • Ownership of the memory cannot be shared or transferred.
  2. std::shared_ptr:
    • std::shared_ptr is a smart pointer that allows multiple pointers to share ownership of the same allocated memory.
    • It keeps track of the number of std::shared_ptr instances pointing to the memory.
    • The memory is deallocated only when the last std::shared_ptr pointing to it is destroyed.
    • It provides a form of automatic garbage collection, as memory is automatically released when it is no longer in use.
  3. std::weak_ptr:
    • std::weak_ptr is a smart pointer that does not participate in ownership of the allocated memory.
    • It is used in conjunction with std::shared_ptr to break cyclic dependencies and prevent memory leaks.
    • It provides a non-owning reference to an object managed by std::shared_ptr.
    • It can be used to check if the memory it points to is still valid, but it does not prevent the memory from being deallocated.

Example Program:

#include <iostream>
#include <memory>

int main() {
    // Using unique_ptr
    std::unique_ptr<int> uniquePtr(new int(42));
    std::cout << "Value stored in uniquePtr: " << *uniquePtr << std::endl;

    // Using shared_ptr
    std::shared_ptr<int> sharedPtr1 = std::make_shared<int>(10);
    std::shared_ptr<int> sharedPtr2 = sharedPtr1;
    std::cout << "Value stored in sharedPtr1: " << *sharedPtr1 << std::endl;
    std::cout << "Value stored in sharedPtr2: " << *sharedPtr2 << std::endl;

    // Using weak_ptr
    std::shared_ptr<int> sharedPtr3 = std::make_shared<int>(20);
    std::weak_ptr<int> weakPtr(sharedPtr3);
    std::cout << "Value stored in weakPtr: ";
    if (auto ptr = weakPtr.lock()) {
        std::cout << *ptr << std::endl;
    } else {
        std::cout << "Memory is no longer valid." << std::endl;
    }

    return 0;
}

Line by Line Explanation:

  • Include necessary header files for the program.
  • Main function begins.
  • Create a std::unique_ptr named uniquePtr pointing to a dynamically allocated integer with value 42.
  • Output the value stored in uniquePtr.
  • Create two std::shared_ptr instances, sharedPtr1 and sharedPtr2, both pointing to a dynamically allocated integer with value 10. Since sharedPtr1 and sharedPtr2 share ownership, they both point to the same memory location.
  • Output the values stored in sharedPtr1 and sharedPtr2.
  • Create a std::shared_ptr named sharedPtr3 pointing to a dynamically allocated integer with value 20. Create a std::weak_ptr named weakPtr pointing to the same memory location as sharedPtr3.
  • Output the value stored in weakPtr. If the memory is still valid (i.e., weakPtr can be converted to a std::shared_ptr), output the value. Otherwise, indicate that the memory is no longer valid.
  • End of the main function.

Smart Pointer – Basic Usage with std::unique_ptr

Introduction: This program demonstrates the basic usage of std::unique_ptr smart pointer.

#include <iostream>
#include <memory>

int main() {
    // Create a unique pointer to an integer
    std::unique_ptr<int> ptr(new int(5));

    // Access the value using the dereference operator
    std::cout << "Value of ptr: " << *ptr << std::endl;

    // Release the memory explicitly
    ptr.reset();

    return 0;
}

Explanation:

  • #include <memory>: Includes the necessary header for smart pointers.
  • std::unique_ptr<int> ptr(new int(5));: Declares a unique pointer ptr to an integer dynamically allocated with value 5.
  • std::cout << "Value of ptr: " << *ptr << std::endl;: Accesses the value pointed by ptr using the dereference operator.
  • ptr.reset();: Releases the memory explicitly by calling the reset method of std::unique_ptr.

This program demonstrates the basic usage of std::unique_ptr smart pointer.

Smart Pointer – Shared Ownership with std::shared_ptr

Introduction: This program demonstrates the shared ownership semantics of std::shared_ptr smart pointer.

#include <iostream>
#include <memory>

void displayCount(std::shared_ptr<int> ptr) {
    std::cout << "Reference count: " << ptr.use_count() << std::endl;
}

int main() {
    // Create a shared pointer to an integer
    std::shared_ptr<int> ptr1(new int(5));

    // Display reference count
    displayCount(ptr1);

    // Create another shared pointer to the same integer
    std::shared_ptr<int> ptr2 = ptr1;

    // Display reference count
    displayCount(ptr1);
    displayCount(ptr2);

    // Release memory
    ptr1.reset();

    // Display reference count
    displayCount(ptr2);

    return 0;
}

Explanation:

  • void displayCount(std::shared_ptr<int> ptr): A function to display the reference count of a shared pointer.
  • std::shared_ptr<int> ptr1(new int(5));: Declares a shared pointer ptr1 to an integer dynamically allocated with value 5.
  • displayCount(ptr1);: Displays the reference count of ptr1.
  • std::shared_ptr<int> ptr2 = ptr1;: Declares another shared pointer ptr2 and shares ownership with ptr1.
  • displayCount(ptr1);, displayCount(ptr2);: Displays the reference count of ptr1 and ptr2.
  • ptr1.reset();: Releases the memory held by ptr1.
  • displayCount(ptr2);: Displays the reference count of ptr2 after releasing the memory by ptr1.

This program demonstrates the shared ownership semantics of std::shared_ptr smart pointer.

Smart Pointer – Avoiding Memory Leaks with std::unique_ptr

Introduction: This program demonstrates how std::unique_ptr helps in avoiding memory leaks.

#include <iostream>
#include <memory>

void createResource() {
    // Create a unique pointer to dynamically allocated memory
    std::unique_ptr<int> ptr(new int(10));

    // Use the resource
    std::cout << "Value of ptr: " << *ptr << std::endl;

    // Memory is automatically released when ptr goes out of scope
}

int main() {
    createResource();

    // No need to release memory explicitly

    return 0;
}

Explanation:

  • void createResource(): A function to demonstrate dynamic memory allocation and automatic memory release.
  • std::unique_ptr<int> ptr(new int(10));: Declares a unique pointer ptr to an integer dynamically allocated with value 10.
  • std::cout << "Value of ptr: " << *ptr << std::endl;: Accesses the value pointed by ptr.
  • Memory allocated by ptr is automatically released when ptr goes out of scope, avoiding memory leaks.

This program demonstrates how std::unique_ptr helps in avoiding memory leaks by automatically releasing dynamically allocated memory when the pointer goes out of scope.

Introduction: Weak Pointer

This program demonstrates the usage of std::weak_ptr smart pointer in C++. Unlike std::shared_ptr, std::weak_ptr does not participate in reference counting and does not keep the managed object alive. It is typically used to break circular references between std::shared_ptrs.

#include <iostream>
#include <memory>

class Node {
public:
    int data;
    std::weak_ptr<Node> next;

    Node(int value) : data(value) {
        std::cout << "Node with data " << data << " created" << std::endl;
    }

    ~Node() {
        std::cout << "Node with data " << data << " destroyed" << std::endl;
    }
};

int main() {
    // Creating shared pointers to nodes
    std::shared_ptr<Node> node1 = std::make_shared<Node>(1);
    std::shared_ptr<Node> node2 = std::make_shared<Node>(2);

    // Creating a weak pointer to node2
    node1->next = node2;

    // Checking if the weak pointer is expired
    if (auto sharedPtr = node1->next.lock()) {
        std::cout << "Data of next node: " << sharedPtr->data << std::endl;
    } else {
        std::cout << "Next node is no longer available" << std::endl;
    }

    // Resetting node2
    node2.reset();

    // Checking if the weak pointer is expired after resetting node2
    if (auto sharedPtr = node1->next.lock()) {
        std::cout << "Data of next node: " << sharedPtr->data << std::endl;
    } else {
        std::cout << "Next node is no longer available" << std::endl;
    }

    return 0;
}

Explanation:

  • class Node: Defines a class representing a node in a linked list. It has an integer data and a weak pointer next to the next node.
  • Node(int value): Constructor initializes the node with the given value.
  • ~Node(): Destructor prints a message when the node is destroyed.
  • main(): The main function demonstrates the usage of std::weak_ptr.
  • std::shared_ptr<Node> node1 = std::make_shared<Node>(1);: Creates a shared pointer to a node with value 1.
  • std::shared_ptr<Node> node2 = std::make_shared<Node>(2);: Creates a shared pointer to a node with value 2.
  • node1->next = node2;: Assigns node2 to the next pointer of node1.
  • node1->next.lock(): Checks if the weak pointer next is still valid. If it is, it returns a shared pointer, otherwise, it returns a null pointer.
  • node2.reset(): Resets node2, effectively destroying it.
  • The program demonstrates how std::weak_ptr can be used to break circular references and how to check if the weak pointer is still valid.

This program illustrates the usage of std::weak_ptr smart pointer in C++.

These examples provide a detailed understanding of smart pointers in C++.