provocationofmind.com

Understanding Smart Pointers in C++

Written on

Smart pointers play a crucial role in C and C++ programming, providing direct access to memory while managing it efficiently.

What is a Pointer?

Pointers are variables that store memory addresses of other variables. They enable indirect access to a variable's value, allowing us to modify it without needing to copy data, which can slow down application performance. Pointers facilitate quicker memory access, particularly beneficial in real-time applications where time-sensitive operations are critical. There are two primary types of pointers: raw pointers and smart pointers.

Raw Pointers

A raw pointer is the conventional pointer we are familiar with. However, it can introduce various issues. Memory allocation and deallocation occur dynamically using the new and delete keywords with raw pointers. If allocated memory isn't properly deallocated, it results in memory leaks. Memory leaks occur when allocated memory is no longer needed, potentially leading to out-of-memory errors, reduced performance, or application crashes. Effective dynamic memory management is essential when using raw pointers.

To illustrate, consider the following example demonstrating a class with a constructor and destructor to observe pointer lifespan.

#include <iostream>

using namespace std;

class Source {

public:

Source() { cout << "Source Constructed." << endl; }

~Source() { cout << "Source Deconstructed." << endl; }

};

int main() {

Source* p = new Source;

delete p;

return 0;

}

The output will be: Source Constructed. Source Deconstructed. If the delete keyword is omitted, the output would only indicate "Source Constructed," resulting in a memory leak. Unlike smart pointers, raw pointers do not automatically deallocate memory.

Smart Pointers

Smart pointers help mitigate memory leak issues. There are three main types of smart pointers.

  1. unique_ptr

Introduced in C++11, unique_ptr replaced auto_ptr, which was removed due to complications. This smart pointer manages the lifetime of arrays and objects, ensuring that only one unique_ptr can own an object at any time. It addresses problems related to dangling pointers (pointers that reference invalid data) and memory leaks, eliminating the need for manual dynamic memory management. When the pointer goes out of scope, the memory it owns is automatically released. Ownership can be transferred to another pointer, but it cannot be copied or shared. Move semantics are employed for ownership transfer. Additionally, C++14 introduced the make_unique function, allowing the creation of unique_ptr without the new keyword.

Replacing the pointer with unique_ptr in the main code retains the same output, even without the delete keyword.

#include <memory> // unique_ptr is found in the memory header

int main() {

unique_ptr<Source> ptr1 = make_unique<Source>(); // Preferred over new

unique_ptr<Source> ptr2 = move(ptr1); // Ownership transferred from ptr1 to ptr2

cout << "Ptr1 is " << (ptr1 ? "not null.n" : "null.n");

cout << "Ptr2 is " << (ptr2 ? "not null.n" : "null.n");

return 0;

}

The output: Source Constructed. Ptr1 is null. Ptr2 is not null. Source Deconstructed.

  1. shared_ptr

Unlike unique_ptr, shared_ptr allows multiple pointers to share ownership of an object. The object is only destroyed when the last pointer referencing it is deallocated. Each time a new pointer pointing to the same object is created, a reference counter increases, and when a pointer goes out of scope, the counter decreases. Memory is released only when the reference count reaches zero. For multiple pointers, a copy of the original pointer should be used. The make_shared function is available for creating shared_ptr, similar to make_unique.

int main() {

shared_ptr<Source> ptr1 = make_shared<Source>();

{

shared_ptr<Source> ptr2 = ptr1; // Shared ownership

}

cout << "Out of scope." << endl;

return 0;

}

The output: Source Constructed. Out of scope. Source Deconstructed. In this example, the shared_ptr is only deallocated after the last pointer has exited its scope. However, this can lead to a problem known as "cyclical ownership."

Consider the following code changes:

class Source {

public:

shared_ptr<Source> src_ptr{};

Source() { cout << "Source Constructed." << endl; }

~Source() { cout << "Source Deconstructed." << endl; }

};

int main() {

shared_ptr<Source> ptr = make_shared<Source>();

ptr->src_ptr = ptr; // Creates a cycle

return 0;

}

The output will only show "Source Constructed." since the pointer won't deallocate. ptr and src_ptr share the object, and src_ptr doesn't go out of scope, causing ptr to remain in memory until reassigned.

  1. weak_ptr

weak_ptr is intended to resolve the issue of "cyclical ownership." It can access an object owned by a shared_ptr without increasing the reference count, allowing for timely memory deallocation without waiting for all pointers to exit their scopes.

int main() {

weak_ptr<Source> weak;

{

shared_ptr<Source> shared = make_shared<Source>();

weak = shared;

}

cout << "Out of scope." << endl;

return 0;

}

The output: Source Constructed. Source Deconstructed. Out of scope. In this instance, the object is destroyed before exiting the second scope, unlike shared_ptr. However, this can lead to "dangling pointers," similar to raw pointers. weak_ptr includes two functions for checking the existence of an object, expired and lock. The use_count function can also be used to determine how many references to the object exist before it is destroyed.

int main() {

weak_ptr<Source> weak;

{

shared_ptr<Source> shared = make_shared<Source>();

weak = shared;

cout << "Number of users: " << weak.use_count() << 'n';

cout << "Object " << (weak.lock() ? "still exists.n" : "does not exist.n");

}

cout << "Object " << (weak.expired() ? "does not exist.n" : "still exists.n");

return 0;

}

The output: Source Constructed. Number of users: 1 Object still exists. Source Deconstructed. Object does not exist. The object is destroyed after exiting the scope because only one user (the shared_ptr) remained. weak_ptr functions solely as an observer.

References: - https://www.learncpp.com/cpp-tutorial - https://roadmap.sh/cpp

Share the page:

Twitter Facebook Reddit LinkIn

-----------------------

Recent Post:

Technology and Environmental Sustainability: A Dual-Edged Challenge

Exploring the dual role of technology in shaping environmental sustainability—both beneficial and detrimental.

The Ethics of Armed Robots in Law Enforcement: A Complex Debate

Exploring the implications of arming robots in policing, examining technology advancements, ethical concerns, and public reactions.

How Technology Can Assist in Establishing and Breaking Habits

Discover how technology can help you build and break habits effectively, with insights on useful apps and tools.

Revolutionizing Marketing Project Management with Adobe's Workfront

Adobe's new Workfront Planning module is set to transform marketing project management, enhancing efficiency and collaboration.

Exploring the Future: Neuralink's Controversial Path to Innovation

Discover the groundbreaking yet controversial technology of Neuralink and its implications for the future of human-computer interaction.

Navigating the Emotional Journey of Entrepreneurship

Explore insights on managing the emotional highs and lows of entrepreneurship from Steve Pruneau, founder of Free Agent Source Inc.

Navigating the Data Science Landscape: Tips for Success

Discover strategies for data scientists to effectively communicate with managers and enhance job satisfaction.

Maximizing Your Writing: 5 AI Strategies and 3 Avoidable Uses

Discover five effective ways AI can enhance your writing, along with three key tasks to avoid using AI for.