You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Smart pointers are wrapper classes (template classes) about a real raw
pointer that manage dynamically allocated memory, ensuring that
resources are properly released when no longer needed.
They provide automatic memory management, reducing the risk of memory
leaks and dangling pointers.
The C++ Standard Library provides three primary types of smart pointers:
std::unique_ptr, std::shared_ptr, and std::weak_ptr.
All smart pointers are scoped pointers. When they leave their scope,
they either cease to exist or decrease their reference count.
When designing our own data structure, useing a raw pointer instead.
In smart pointers, the pointer operator (or arrow operator, ->) has been
overloaded. Since the compiler optimizes pointer operator overloading,
obj_ptr->_mem and (*obj_ptr)._memare equivalent to
(obj_ptr)->->_mem and (*obj_ptr)->_mem.
Additionally, the pointer operator can be overloaded to obtain the memory
offsets of an object's members.
Their header file is <memory>.
Unique Pointers (std::unique_ptr)
Explanation
std::unique_ptr is a smart pointer that provides exclusive ownership of a
resource.
Only one std::unique_ptr can own a resource at a time, and its
copy-assignment operator is deleted to avoid mistakes, so it cannot be
copied.
However, it can be moved to transfer ownership.
When a std::unique_ptrgoes out of scope, the resource is
automatically deleted.
It creates an object on the heap and automatically deletes it when it
goes out of scope, without needing to use delete. Since it is
created on the stack, there is no overhead.
Declaration Syntax
std::unique_ptr< Type > uptr;
Initialization Syntax
std::unique_ptr< Type > uptr( new Type( ... ) );
// Common. Recommend.
std::unique_ptr< Type > uptr
= std::make_unique< Type >( /* constructor arguments */ );
std::unique_ptr< Type > uptr1
= std::make_unique< Type >( /* constructor arguments */ );
// Move constructor.
std::unique_ptr< Type > uptr2( std::move( uptr1 ) );
// Optionally set uptr1 to nullptr for clarity.
uptr1 = nullptr;
// Constructs a `std::unique_ptr` which owns `rptr`, initializing the stored pointer with `rptr`.
Type* rptr = new Type( ... );
std::unique_ptr< Type > uptr( rptr );
// Optionall set rptr to nullptr to indicate it no longer points to a valid object.
rptr = nullptr;
pointer: std::remove_reference< Deleter >::type::pointer if that type
exists, otherwise T*. Must satisfy NullablePointer.
element_type: T, the type of the object managed by this unique_ptr.
deleter_type: Deleter, the function object or lvalue reference to
function or to function object, to be called from the destructor.
Member Functions
(constructor): Constructs a new unique_ptr (public member function).
(destructor): Destructs the managed object if such is present (public member
function).
operator=: Assigns the unique_ptr (public member function).
release: Returns a pointer to the managed object and releases the ownership
(public member function).
reset: Replaces the managed object (public member function).
swap: Swaps the managed objects (public member function).
get: Returns a pointer to the managed object (public member function).
get_deleter: Returns the deleter that is used for destruction of the
managed object (public member function).
operator bool: Checks if there is an associated managed object (public
member function).
operator*, operator->: Dereferences pointer to the managed object
(public member function).
operator[]: Provides indexed access to the managed array (public member
function).
Non-member Functions
make_unique (C++14), make_unique_for_overwrite (C++20): Creates a unique
pointer that manages a new object (function template).
operator!= (removed in C++20), operator==/</<=/>/>=/<=> (C++20): Compares
to another unique_ptr or with nullptr (function template).
operator<<( std::unique_ptr ) (C++20): Outputs the value of the managed
pointer to an output stream (function template).
std::swap( std::unique_ptr ): Specializes the std::swap algorithm
(function template).
Helper Classes
std::hash< std::unique_ptr >: Hash support for std::unique_ptr (class
template specialization).
Shared Pointers (std::shared_ptr)
Explanation
std::shared_ptr is a type of smart pointer that enables multiple
instances of std::shared_ptr to collectively manage the ownership of
a resource.
The implementation of std::shared_ptr is contingent upon the specific
compiler and standard library employed. Most systems implement this
functionality using reference counting.
Reference counting maintains a record of the number of references
pointing to a shared pointer:
Each time a new reference is established, the reference count is
incremented by one. (Constructors)
Conversely, each time a reference is removed, the count is
decremented by one.
When the reference count is reduced to zero, the associated shared
pointer is automatically deallocated. (Destructors)
This reference counting mechanism introduces a degree of overhead.
Notably, assigning one std::shared_ptr to another results in an increase in
the reference count, while assigning a std::shared_ptr to a
std::weak_ptr does not affect the reference count.
Declaration Syntax
std::shared_ptr< Type > sptr;
Initialization Syntax
// Not recommended. The reference count and the object will be constructed separately.
std::shared_ptr< Type > sptr( new Type( ... ) );
// Recommended. The reference count and the object will be constructed together, which is more efficient.
std::shared_ptr< Type > sptr = std::make_shared< Type >( /* constructor arguments */ );
// Recommended. The reference count and the object will be constructed together, which is more efficient.
std::shared_ptr< Type > sptr1 = std::make_shared< Type >( /* constructor arguments */ );
// Copy constructor, the reference count is increased by one.
std::shared_ptr< Type > sptr2 = sptr1;
// Recommended. The reference count and the object will be constructed together, which is more efficient.
std::shared_ptr< Type > sptr1 = std::make_shared< Type >( /* constructor arguments */ );
// Move constructors.
std::shared_ptr< Type > sptr2 = std::move( sptr1 );
// Optionally set sptr1 to nullptr for clarity.
sptr1 = nullptr;
// Constructs a `std::shared_ptr` which owns `rptr`, initializing the stored pointer with `rptr`.
Type* rptr = new Type( ... );
std::shared_ptr< Type > sptr( rptr );
// Optionall set rptr to nullptr to indicate it no longer points to a valid object.
rptr = nullptr;
// Constructs a `std::shared_ptr` which owns `uptr`, initializing the stored pointer with `uptr`.
std::unique_ptr< Type > uptr
= std::make_unique< Type >( /* constructor arguments */ );
std::shared_ptr< Type > sptr = std::move( uptr );
// Optionally set uptr to nullptr for clarity.
uptr = nullptr;
element_type: T (until C++17), std::remove_extent_t< T >. (since C++17)
weak_type (since C++17): std::weak_ptr< T >.
Member Functions
(constructor): Constructs new shared_ptr (public member function).
(destructor): Destructs the owned object if no more shared_ptrs link to it
(public member function).
operator=: Assigns the shared_ptr (public member function).
reset: Replaces the managed object (public member function).
swap: Swaps the managed objects (public member function).
get: Returns the stored pointer (public member function).
operator*, operator->: Dereferences the stored pointer (public member
function).
operator[] (C++17): Provides indexed access to the stored array (public
member function).
use_count: Returns the number of shared_ptr objects referring to the same
managed object (public member function).
unique (until C++20): Checks whether the managed object is managed only by
the current shared_ptr object (public member function).
operator bool: Checks if the stored pointer is not null (public member
function).
owner_before: Provides owner-based ordering of shared pointers (public
member function).
owner_hash (C++26): Provides owner-based hashing of shared pointers
(public member function).
owner_equal (C++26): provides owner-based equal comparison of shared
pointers (public member function).
Non-member Functions
make_shared (C++20), make_shared_for_overwrite (C++20): Creates a shared
pointer that manages a new object (function template).
allocate_shared (C++20), allocate_shared_for_overwrite (C++20): Creates a
shared pointer that manages a new object allocated using an allocator
(function template).
static_pointer_cast (C++17), dynamic_pointer_cast (C++17),
const_pointer_cast (C++17), reinterpret_pointer_cast (C++17): Applies
static_cast, dynamic_cast, const_cast, or reinterpret_cast to the
stored pointer (function template).
get_deleter: returns the deleter of specified type, if owned (function
template).
operator==, operator!=/</<=/>/>= (removed in C++20), operator<=>
(C++20): Compares with another shared_ptr or with nullptr (function
template).
operator<<( std::shared_ptr ): Outputs the value of the stored pointer to
an output stream (function template).
std::swap( std::shared_ptr ): Specializes the std::swap algorithm
(function template).
std::hash< std::shared_ptr >: Hash support for std::shared_ptr (class
template specialization).
Weak Pointers (std::weak_ptr)
Explanation
std::weak_ptr is a variant of shared pointer that does not maintain a
reference count.
It does not take ownership of the managed object but allows tracking of
an object managed by std::shared_ptr without affecting the reference
count.
This feature is particularly useful for avoiding circular references and
ensuring that memory is not deallocated.
Since std::weak_ptr does not increment the reference count of the resource,
it does not influence the resource's lifetime.
To access the managed resource, a std::weak_ptr must be converted to
a std::shared_ptr using the lock() method.
Assigning a shared pointer to a raw pointer can lead to several
issues after the shared pointer has been released, particularly in a
multithreaded context where a raw pointer may be deleted multiple
times.
In contrast, std::weak_ptr provides a solution to these problems.
If two objects reference one another, at least one of the referencesmust be a weak pointer to prevent circular dependencies.
Although std::weak_ptr does not maintain the resources's lifetime, the best
practice is calling reset() if we do not need a std::weak_ptr.
Declaration Syntax
std::weak_ptr< Type > wptr;
Initialization Syntax
std::shared_ptr< Type > sptr = std::make_shared< Type >( /* constructor arguments */ );
std::weak_ptr< Type > wptr = sptr;
std::shared_ptr< Type > sptr = std::make_shared< Type >( /* constructor arguments */ );
std::weak_ptr< Type > wptr1 = sptr;
// Copy constructor.
std::weak_ptr< Type > wptr2 = wptr1;
std::shared_ptr< Type > sptr = std::make_shared< Type >( /* constructor arguments */ );
std::weak_ptr< Type > wptr1 = sptr;
// Move constructor.
std::weak_ptr< Type > wptr2 = std::move( wptr1 );
std::shared_ptr< Type > sptr = std::make_shared< Type >( /* constructor arguments */ );
std::weak_ptr< Type > wptr = sptr;
wptr.reset();
std::shared_ptr< Type > sptr1 = std::make_shared< Type >( /* constructor arguments */ );
std::weak_ptr< Type > wptr = sptr1;
auto sptr2 = wptr.lock();
if( sptr2 ) {
...;
} else {
...;
};
std::shared_ptr< Type > sptr1 = std::make_shared< Type >( /* constructor arguments */ );
std::weak_ptr< Type > wptr = sptr1;
// C++17.if( auto sptr2 = wptr.lock(); ) {
...;
} else {
...;
};
Circular References
Explanation
A circular reference occurs when two or more objects reference each other
in a way that creates a cycle.
This happens when Object A holds a reference to Object B, and Object B holds
a reference back to Object A (or when a longer chain of objects reference
each other in a loop).
This can cause memory leaks or infinite loops.
The best approach is to use a weak_ptr for references between objects.
Examples
// Circular references occur.
#include<iostream>
#include<memory>structNode {
std::shared_ptr< Node > next;
~Node() { std::cout << "Node destroyed\n"; };
};
intmain() {
auto nodeA = std::make_shared< Node >();
auto nodeB = std::make_shared< Node >();
std::cout << nodeA.use_count() << std::endl;
std::cout << nodeB.use_count() << std::endl;
nodeA->next = nodeB; // nodeB.use_count() + 1;
nodeB->next = nodeA; // nodeA.use_count() + 1; Creates a circular reference
std::cout << nodeA.use_count() << std::endl;
std::cout << nodeB.use_count() << std::endl;
// Both nodes will never be deleted due to circular referencereturn0;
};
// Circular references do not occur.
#include<iostream>
#include<memory>structNode {
std::weak_ptr< Node > next;
~Node() { std::cout << "Node destroyed\n"; };
};
intmain() {
auto nodeA = std::make_shared< Node >();
auto nodeB = std::make_shared< Node >();
std::cout << nodeA.use_count() << std::endl;
std::cout << nodeB.use_count() << std::endl;
nodeA->next = nodeB;
nodeB->next = nodeA;
std::cout << nodeA.use_count() << std::endl;
std::cout << nodeB.use_count() << std::endl;
// Both nodes will be deleted.return0;
};