Smart Pointers

Smart pointers are objects in C++ that mimic the behavior of raw pointers but provide additional features such as automatic memory management. They help in avoiding common pitfalls associated with manual memory management, such as memory leaks and dangling pointers.

Basic Concepts

  • Raw Pointers: Traditional pointers in C++ that directly manage memory addresses.
  • Ownership: Smart pointers manage the lifetime of dynamically allocated memory by automatically deallocating it when the pointer goes out of scope.

Types of Smart Pointers

  1. unique_ptr: Manages a dynamically allocated object and ensures that only one pointer owns it.
  2. shared_ptr: Allows multiple pointers to share ownership of a dynamically allocated object.
  3. weak_ptr: Observes an object managed by shared_ptr without affecting its ownership, preventing circular dependencies.

unique_ptr in Depth

Creation and Usage

In this section, we’ll explore the creation and usage of unique_ptr objects in C++. unique_ptr provides exclusive ownership of dynamically allocated memory, ensuring that only one unique_ptr owns the resource at any given time.

				
					#include <iostream>
#include <memory>

void uniquePtrExample() {
    // Creating a unique_ptr using std::make_unique
    std::unique_ptr<int> ptr = std::make_unique<int>(10);

    // Accessing the value stored in the dynamically allocated memory
    std::cout << "Value: " << *ptr << std::endl;
} // Memory automatically deallocated when 'ptr' goes out of scope

int main() {
    uniquePtrExample();
    return 0;
}

				
			
				
					// output //
Value: 10

				
			

Explanation:

  • We include the <memory> header to use unique_ptr.
  • Inside the uniquePtrExample function, we create a unique_ptr named ptr using std::make_unique<int>(10). This dynamically allocates memory for an integer with value 10.
  • We then dereference ptr and print the value stored in the dynamically allocated memory using *ptr.
  • After the function uniquePtrExample exits, the unique_ptr ptr goes out of scope, and the dynamically allocated memory is automatically deallocated.

Move Semantics

Move semantics allow transferring ownership from one unique_ptr to another efficiently, ensuring that only one unique_ptr owns the resource at any given time.

				
					#include <iostream>
#include <memory>

void moveSemanticsExample() {
    // Creating a unique_ptr
    std::unique_ptr<int> ptr1 = std::make_unique<int>(20);

    // Transferring ownership using move semantics
    std::unique_ptr<int> ptr2 = std::move(ptr1);

    // Attempting to access the value stored in ptr1 (now nullptr)
    // This will cause a runtime error
    // std::cout << "Value: " << *ptr1 << std::endl;
}

int main() {
    moveSemanticsExample();
    return 0;
}

				
			
				
					// output //
Runtime error: Segmentation fault (core dumped)

				
			

Explanation:

  • Inside the moveSemanticsExample function, we create a unique_ptr ptr1 and allocate memory for an integer with value 20.
  • We then transfer ownership of the dynamically allocated memory from ptr1 to ptr2 using std::move(ptr1).
  • Attempting to access the value stored in ptr1 after the move operation will result in a runtime error (segmentation fault) since ptr1 is now nullptr. This demonstrates that ownership of the memory has been transferred to ptr2.

shared_ptr in Depth

Creation and Usage

In this section, we’ll explore the creation and usage of shared_ptr objects in C++. shared_ptr allows multiple pointers to share ownership of the same dynamically allocated memory, ensuring that the memory is deallocated only when all shared_ptr instances owning it are destroyed.

				
					#include <iostream>
#include <memory>

void sharedPtrExample() {
    // Creating a shared_ptr using std::make_shared
    std::shared_ptr<int> ptr1 = std::make_shared<int>(30);

    // Creating another shared_ptr that shares ownership with ptr1
    std::shared_ptr<int> ptr2 = ptr1;

    // Accessing the value stored in the dynamically allocated memory
    std::cout << "Value: " << *ptr1 << std::endl;
    std::cout << "Value: " << *ptr2 << std::endl;
} // Memory deallocated when both ptr1 and ptr2 go out of scope

int main() {
    sharedPtrExample();
    return 0;
}

				
			
				
					// output //
Value: 30
Value: 30

				
			

Explanation:

  • Inside the sharedPtrExample function, we create a shared_ptr ptr1 using std::make_shared<int>(30). This dynamically allocates memory for an integer with value 30.
  • We then create another shared_ptr ptr2 and initialize it with ptr1. Both ptr1 and ptr2 now share ownership of the same dynamically allocated memory.
  • We dereference ptr1 and ptr2 to access the value stored in the dynamically allocated memory, and both pointers print the same value, demonstrating shared ownership.
  • After the function sharedPtrExample exits, both ptr1 and ptr2 go out of scope, and the dynamically allocated memory is deallocated.

Reference Counting

shared_ptr uses reference counting to keep track of the number of shared_ptr instances owning the dynamically allocated memory. Memory is deallocated when the reference count reaches zero.

				
					#include <iostream>
#include <memory>

void referenceCountingExample() {
    // Creating a shared_ptr
    std::shared_ptr<int> ptr1 = std::make_shared<int>(40);

    // Creating another shared_ptr that shares ownership with ptr1
    std::shared_ptr<int> ptr2 = ptr1;

    // Checking the reference count
    std::cout << "Reference count: " << ptr1.use_count() << std::endl;
} // Memory deallocated when both ptr1 and ptr2 go out of scope

int main() {
    referenceCountingExample();
    return 0;
}

				
			
				
					// output //
Reference count: 2

				
			

Explanation:

  • Inside the referenceCountingExample function, we create a shared_ptr ptr1 and dynamically allocate memory for an integer with value 40.
  • We then create another shared_ptr ptr2 and initialize it with ptr1, causing both pointers to share ownership of the same memory.
  • We use the use_count() function to retrieve the reference count of ptr1, which returns 2 since both ptr1 and ptr2 share ownership.
  • After the function referenceCountingExample exits, both ptr1 and ptr2 go out of scope, and the dynamically allocated memory is deallocated.

weak_ptr in Depth

Creation and Usage

In this section, we’ll explore the creation and usage of weak_ptr objects in C++. weak_ptr allows observing an object managed by shared_ptr without affecting its ownership, preventing potential issues like circular dependencies and memory leaks.

				
					#include <iostream>
#include <memory>

void weakPtrExample() {
    // Creating a shared_ptr
    std::shared_ptr<int> sharedPtr = std::make_shared<int>(50);

    // Creating a weak_ptr from shared_ptr
    std::weak_ptr<int> weakPtr = sharedPtr;

    // Accessing the value stored in the dynamically allocated memory
    if (auto ptr = weakPtr.lock()) {
        std::cout << "Value: " << *ptr << std::endl;
    } else {
        std::cout << "Pointer expired" << std::endl;
    }
}

int main() {
    weakPtrExample();
    return 0;
}

				
			
				
					// output //
Value: 50

				
			

Explanation:

  • Inside the weakPtrExample function, we first create a shared_ptr sharedPtr and dynamically allocate memory for an integer with value 50.
  • We then create a weak_ptr weakPtr and initialize it with sharedPtr. The weak_ptr observes the shared_ptr without affecting its ownership.
  • We use the lock() function to obtain a shared_ptr from the weak_ptr. If the shared_ptr is still valid (i.e., the object has not been deleted), we dereference it and print the value stored in the dynamically allocated memory.
  • Since the shared_ptr sharedPtr is still valid at this point, we successfully print the value 50.

Breaking Cycles

weak_ptr is often used to break strong reference cycles among shared_ptrs, preventing memory leaks and circular dependencies.

				
					#include <iostream>
#include <memory>

class Node {
public:
    std::weak_ptr<Node> next;
    // Other members...
};

void breakingCyclesExample() {
    std::shared_ptr<Node> node1 = std::make_shared<Node>();
    std::shared_ptr<Node> node2 = std::make_shared<Node>();

    // Creating a circular reference
    node1->next = node2;
    node2->next = node1; // Causes a circular reference

    // Breaking the cycle using weak_ptrs
    node1->next = std::weak_ptr<Node>(node2);
    node2->next = std::weak_ptr<Node>(node1);
}

int main() {
    breakingCyclesExample();
    return 0;
}

				
			

Explanation:

  • Inside the breakingCyclesExample function, we create two shared_ptrs node1 and node2, each pointing to a Node object.
  • We create a circular reference by setting node1‘s next pointer to node2 and node2‘s next pointer to node1.
  • This creates a strong reference cycle, where neither node1 nor node2 can be deallocated because they both hold a strong reference to each other.
  • To break the cycle, we use weak_ptrs to represent the next pointer. This allows the Node objects to be deallocated once all strong references to them are released, preventing memory leaks and breaking the circular dependency.

Smart pointers in C++, including unique_ptr, shared_ptr, and weak_ptr, provide powerful tools for managing dynamic memory and avoiding common pitfalls associated with manual memory management. By understanding their features and usage, developers can write safer, more robust code with fewer memory-related issues.Happy coding !❤️

Table of Contents

Contact here

Copyright © 2025 Diginode

Made with ❤️ in India