Move Constructor & Assignment Operator With std::shared_ptr
In an earlier article , we have seen how move constructor & move assignment operators helped us in creating our own unique_ptr . Here we will use move constructor & assignment operator to implement unsophisticated shared_ptr.
Implementing Our shared_ptr with Move Constructor & Assignment Operator #
- In some cases, we have a requirement where a single resource is represented by multiple pointers. We can not accomplish this by std::unique_ptr . To accomplish this, we can add a new variable to our smart pointer class which keeps track of reference count at the real-time. And when the reference count goes to zero which means nobody is using that resource, we will deallocate that resource.
- Unlike std::unique_ptr , which is designed to singly own and manage a resource, std::shared_ptr is meant to solve the case where you need multiple smart pointers co-owning a resource.
- Unlike std::unique_ptr , which uses a single pointer internally, std::shared_ptr uses two pointers internally. One pointer points at the managed resource. The other points at a “control block”, which is a dynamically allocated object that tracks of a bunch of stuff, including how many std::shared_ptr are pointing at the resource.
- Here I have only used a single variable to keep track of references pointing to resource for simplicity. The actual implementation is a bit bulky for more feature & security purpose.
A bit about move constructor & move assignment operator #
When does the move constructor & move assignment operator get called?
The move constructor and move assignment are called when those functions have been defined, and the argument for construction or assignment is an r-value . Most typically, this r-value will be a literal or temporary value.
- In most cases, a move constructor and move assignment operator will not be provided by default, unless the class does not have any defined copy constructors, copy assignment, move assignment, or destructors. However, the default move constructor and move assignment do the same thing as the default copy constructor and copy assignment ( make copies, not do moves ).
l-value reference & r-value reference #
- I have already written a separate article for that.
std::move #
- In C++11, std::move is a standard library function that serves a single purpose – to convert its argument into an r-value .
- Once you start using move semantics more regularly, you’ll start to find cases where you want to invoke move semantics, but the objects you have to work with are l-values , not r-values .
Use case or benefit of std::move #
- Consider the following swap() function as an example:
- Above swap() function makes 3 copies. That leads to a lot of excessive string creation and destruction, which is slow.
- However, doing copies isn’t necessary here. All we’re really trying to do is swap the values of a and b , which can be accomplished just as well using 3 moves instead! So if we switch from copy semantics to move semantics, we can make our code more performant.
- std::move can also be useful when sorting an array of elements. Many sorting algorithms (such as selection sort and bubble sort) work by swapping pairs of elements. Here we can use move semantics, which is more efficient.
- C++ Data Types
- C++ Input/Output
- C++ Pointers
- C++ Interview Questions
- C++ Programs
- C++ Cheatsheet
- C++ Projects
- C++ Exception Handling
- C++ Memory Management
How to Implement User Defined Shared Pointers in C++?
shared_ptr is one of the smart pointer introduced as a wrapper to the old raw pointers in C++ to help in avoiding the risks and errors of raw pointers. In this article, we will learn how to implement our own user defined shared pointer in C++.
What is shared_ptr in C++?
A std::shared_ptr is a container for raw pointers. It is a reference counting ownership model i.e. it maintains the reference count of its contained pointer in cooperation with all copies of the std::shared_ptr. So, the counter is incremented each time a new pointer points to the resource and decremented when destructor of the object is called.
Reference Counting
It is a technique of storing the number of references, pointers or handles to a resource such as an object, block of memory, disk space or other resources.
An object referenced by the contained raw pointer will not be destroyed until reference count is greater than zero i.e. until all copies of std::shared_ptr have been deleted.
Points to Consider when Counting Number of References
When a new shared pointer is constructed from the raw pointer, initialize the reference counter as 1. When a new shared pointer is assigned with some other shared pointer, the reference counter for that reference is increased by one. When an old shared pointer is assigned with some other shared pointer (both pointing to different memory location), the count of the reference pointed by the first shared pointer is decreased while the count of the second shared pointer is increased.
When to Use Shared Pointer?
We should use shared_ptr when we want to assign one raw pointer to multiple owners. For more information and details about shared and other smart pointers, please read here .
C++ Program for User Defined Implementation of Shared Pointer
Similar reads, please login to comment..., improve your coding skills with practice.
What kind of Experience do you want to share?
shared_ ptr - basics and internals with examples
An introduction to shared_ptr's basics and internals through examples.
1. Overview
The C++11 std::shared_ptr< T > is a shared ownership smart pointer type. Several shared_ptr instances can share the management of an object's lifetime through a common control block . The managed object is deleted when the last owning shared_ptr is destroyed (or is made to point to another object). Memory management by shared_ptr is deterministic because the timing of a managed object's destruction is predictable and in the developer's control. Hence, std::shared_ptr brings deterministic automatic memory management to C++, without the overhead of garbage collection. Here is a basic example of shared_ptr :
This article is specific to general usage and internals of shared_ptr . It does not cover the fundamentals of smart pointers and assumes that the reader is familiar with those. Having looked at the basic usage, let's move on to the internals of shared_ptr that make it work.
2. Internals
In a typical implementation, a shared_ptr contains only two pointers: a raw pointer to the managed object that is returned by get() , and a pointer to the control block. A shared_ptr control block at least includes a pointer to the managed object or the object itself, a reference counter, and a weak counter. And depending on how a shared_ptr is initialized, the control block can also contain other data, most notably, a deleter and an allocator. The following figure corresponds to the example in the previous section. It shows the conceptual memory layout of the two shared_ptr instances managing the object:
Next, we talk about the details of the most relevant parts of the control block. You would see that the memory layout of shared_ptr can deviate from the above illustration depending on how it is constructed.
2.1. Pointer to Managed Object (or Managed Object)
A control block contains a pointer to the managed object, which is used for deleting the object. One interesting fact is that the managed pointer in the control block could be different in type (and even value) from the raw pointer in the shared_ptr . This leads to a few fascinating use cases. In the following example, the types of the raw pointer and the managed pointer are different, but they are compatible and have the same values:
The inheritance example above is rather contrived. It shows that despite the destructor being not virtual , the correct derived class ( B ) destructor is invoked when the base class ( A ) shared_ptr is reset . That works because the control block is destroying the object through B* , not through the raw pointer A* . Nevertheless, the destructor should be declared virtual in the classes that are meant to be used polymorphically. This example intends to merely show how a shared_ptr works.
There is an even more exotic aliasing constructor of shared_ptr that can initialize a shared_ptr from a raw pointer and an unrelated shared_ptr . Consequently, an aliasing constructor can produce a shared_ptr that shares the management of one object but points to another object (usually a subobject of the managed object). For instance:
The in-depth treatment of aliasing constructor deserves its own space. I encourage you to check out " Aliasing constructed shared_ptr as key of map or set " for a more persuasive use case.
There is more discussion about the managed object pointer in the 'Deleter' section below when we talk about the type erasure .
Before we delve into more intricate details, let's talk about the std::make_shared . We mentioned above that the control block could either contain a pointer to the managed object or the object itself. The control block is dynamically allocated. Constructing the managed object in-place within the control block can avoid the two separate memory allocations for the object and the control block, resulting in an uncomplicated control block and better performance. The std::make_shared is a preferred way to construct a shared_ptr because it builds the managed object within the control block:
2.2. Reference Counter
The reference counter, which is incremented and decremented atomically, tracks the number of owning shared_ptr instances. The reference count increases as a new shared_ptr is constructed, and it decreases as an owning shared_ptr is destroyed. One exception to that is the reference count is left unchanged when a shared_ptr is moved because the move-constructor transfers the ownership from the source to the newly constructed shared_ptr . The managed object is disposed of when the reference count reaches zero.
std::shared_ptr ownership is also affected by the copy and move assignment operators. The copy assignment operator decreases the reference count of the destination (LHS) shared_ptr and increases the reference count of the source (RHS) shared_ptr . Whereas, the move assignment operator decreases the reference count of the destination (LHS) but does not change the reference count of the source (RHS).
Let's explore another example that exhibits the lifecycle of an object managed by a few shared_ptr instances. As you go through the code, refer the following figure for the different stages:
2.3. Weak Counter
A control block also keeps the count of weak_ptr associated with it in a weak counter. An std::weak_ptr is a smart pointer that serves as a weak reference to an std::shared_ptr managed object. When a weak_ptr is created from a shared_ptr , it refers to the same control block but does not share the ownership of the managed object. It is not possible to directly access the managed object through a weak_ptr . A weak_ptr must be copied to a shared_ptr to acquire access to the managed object.
The following multithreaded example shows how a shared_ptr can be created from a weak_ptr as long as the managed object is alive. A reader thread periodically tries to acquire a shared_ptr< std::atomic_int > from a weak_ptr< std::atomic_int > and logs the value. If the reader thread cannot acquire a shared_ptr in an iteration, it exits. A writer thread periodically changes the shared_ptr managed std::atomic_int value a few times and exits. When the writer thread exits, the shared_ptr held by it is destroyed, and the reader thread can no longer get a shared_ptr from its weak_ptr , which makes the reader thread to also exit. The program terminates when both the threads exit:
The weak count is the number of existing weak_ptr . The weak count does not play any role in deciding the lifetime of the managed object, which is deleted when the reference count reaches zero. However, the control block itself is not deleted until the weak count also reaches zero.
2.4. Deleter
When a shared_ptr is initialized with a pointer, its control block contains a deleter function object (or function pointer), which is invoked to destroy the managed object. If a custom deleter is not provided to the shared_ptr constructor, a default deleter (e.g., std::default_delete ) is used that calls the delete operator.
The deleter is type-erased for two reasons. First, a deleter is an optional argument to a shared_ptr constructor, not a template parameter. Hence, a shared_ptr's type is deleter agnostic. Second, a deleter is a function object (or a function pointer), e.g., function< void(T*) > . This indirection makes shared_ptr independent of the details of how the managed object is deleted. This loose-coupling of shared_ptr with the deleter makes it quite flexible. For instance, in the example below, a vector<shared_ptr<T>> can be in its compilation unit entirely oblivious to the knowledge of how an incomplete type T is deleted:
2.5. Allocator
The control block itself is allocated by an allocator that must satisfy the Allocator requirements. When a custom allocator is not provided, the std::allocator is used that dynamically allocates the control block. The control block keeps a copy of the allocator, which is type-erased like the deleter. There are two ways to use a custom allocator. One is to provide a custom allocator when initializing the shared_ptr with a managed object pointer, as shown below. Note that this shared_ptr constructor also requires a deleter:
Another way to use a custom allocator is to utilize std::allocate_shared that can construct the managed object in-place within a custom allocated control block. Therefore, the std::allocate_shared is like std::make_shared , except that it takes a custom allocator:
3. Conclusion
The std::shared_ptr< T > is a handy yet straightforward utility. But under its simplicity lie extensive details that make it work. Dereferencing a shared_ptr is nearly as fast as a raw pointer, but constructing or copying a shared_ptr is certainly more expensive. Nonetheless, for most applications, this cost is reasonable for automatic memory management.
4. References
std::shared_ptr - cppreference
std::weak_ptr - cppreference
Effective Modern C++ - Scott Meyers
cppreference.com
Std::shared_ptr<t>:: reset.
Replaces the managed object with an object pointed to by ptr . Optional deleter d can be supplied, which is later used to destroy the new object when no shared_ptr objects own it. By default, delete expression is used as deleter. Proper delete expression corresponding to the supplied type is always selected, this is the reason why the function is implemented as template using a separate parameter Y .
If * this already owns an object and it is the last shared_ptr owning it, the object is destroyed through the owned deleter.
If the object pointed to by ptr is already owned, the function generally results in undefined behavior.
[ edit ] Parameters
[ edit ] return value, [ edit ] exceptions, [ edit ] example.
Possible output:
[ edit ] See also
- Recent changes
- Offline version
- What links here
- Related changes
- Upload file
- Special pages
- Printable version
- Permanent link
- Page information
- In other languages
- This page was last modified on 7 October 2023, at 08:00.
- Privacy policy
- About cppreference.com
- Disclaimers
std:: shared_ptr
std::shared_ptr is a smart pointer that retains shared ownership of an object through a pointer. Several shared_ptr objects may own the same object. The object is destroyed and its memory deallocated when either of the following happens:
- the last remaining shared_ptr owning the object is destroyed;
- the last remaining shared_ptr owning the object is assigned another pointer via operator= or reset() .
The object is destroyed using delete-expression or a custom deleter that is supplied to shared_ptr during construction.
A shared_ptr can share ownership of an object while storing a pointer to another object. This feature can be used to point to member objects while owning the object they belong to. The stored pointer is the one accessed by get ( ) , the dereference and the comparison operators. The managed pointer is the one passed to the deleter when use count reaches zero.
A shared_ptr may also own no objects, in which case it is called empty (an empty shared_ptr may have a non-null stored pointer if the aliasing constructor was used to create it).
All specializations of shared_ptr meet the requirements of CopyConstructible , CopyAssignable , and LessThanComparable and are contextually convertible to bool .
All member functions (including copy constructor and copy assignment) can be called by multiple threads on different instances of shared_ptr without additional synchronization even if these instances are copies and share ownership of the same object. If multiple threads of execution access the same shared_ptr without synchronization and any of those accesses uses a non-const member function of shared_ptr then a data race will occur; the shared_ptr overloads of atomic functions can be used to prevent the data race.
Member types
Member functions, non-member functions, helper classes.
The ownership of an object can only be shared with another shared_ptr by copy constructing or copy assigning its value to another shared_ptr . Constructing a new shared_ptr using the raw underlying pointer owned by another shared_ptr leads to undefined behavior.
std::shared_ptr may be used with an incomplete type T . However, the constructor from a raw pointer ( template < class Y > shared_ptr ( Y * ) ) and the template < class Y > void reset ( Y * ) member function may only be called with a pointer to a complete type (note that std::unique_ptr may be constructed from a raw pointer to an incomplete type).
Implementation notes
In a typical implementation, std::shared_ptr holds only two pointers:
- the stored pointer (one returned by get ( ) );
- a pointer to control block .
The control block is a dynamically-allocated object that holds:
- either a pointer to the managed object or the managed object itself;
- the deleter (type-erased);
- the allocator (type-erased);
- the number of shared_ptr s that own the managed object;
- the number of weak_ptr s that refer to the managed object.
When shared_ptr is created by calling std::make_shared or std::allocate_shared , the memory for both the control block and the managed object is created with a single allocation. The managed object is constructed in-place in a data member of the control block. When shared_ptr is created via one of the shared_ptr constructors, the managed object and the control block must be allocated separately. In this case, the control block stores a pointer to the managed object.
The pointer held by the shared_ptr directly is the one returned by get() , while the pointer/object held by the control block is the one that will be deleted when the number of shared owners reaches zero. These pointers are not necessarily equal.
The destructor of shared_ptr decrements the number of shared owners of the control block. If that counter reaches zero, the control block calls the destructor of the managed object. The control block does not deallocate itself until the std::weak_ptr counter reaches zero as well.
In practical implementations, the number of weak pointers may be incremented if there is a shared pointer to the same control block.
To satisfy thread safety requirements, the reference counters are typically incremented using an equivalent of std::atomic::fetch_add with std::memory_order_relaxed (decrementing requires stronger ordering to safely destroy the control block).
Possible output:
IMAGES
VIDEO
COMMENTS
std::shared_ptr<A> p1 = obj; std::shared_ptr<A> p2 = obj; What happens when the pointers fall out of scope? Each assignment creates its own reference counter, since there is no way p2 can know about the one created by p1. In effect, obj is deleted twice - UB. Preferred solution is to use library function std::make_shared. It offers a number of ...
std::shared_ptr is a smart pointer that retains shared ownership of an object through a pointer. Several shared_ptr objects may own the same object. The object is destroyed and its memory deallocated when either of the following happens: the last remaining shared_ptr owning the object is destroyed; ; the last remaining shared_ptr owning the object is assigned another pointer via operator= or ...
The copy assignments (1) adds the object as a shared owner of x's assets, increasing their use_count. The move assignments (2) transfer ownership from x to the shared_ptr object without altering the use_count. x becomes an empty shared_ptr (as if default-constructed). Likewise, the move assignments from other managed pointer types (3) also transfer ownership, and are initialized with set a use ...
std::shared_ptr is one of the smart pointers introduced in C++11. Unlike a simple pointer, it has an associated control block that keeps track of the reference count for the managed object. This reference count is shared among all the copies of the shared_ptr instances pointing to the same object, ensuring proper memory management and deletion.
shared_ptr::operator= Modifiers: shared_ptr::reset. shared_ptr::swap. Observers: ... If * this already owns an object and it is the last shared_ptr owning it, and r is not the same as * this, the object is destroyed through the owned deleter. ... After the assignment, * this contains the pointer previously held by r, and use_count == 1; ...
In an earlier article, we have seen how move constructor & move assignment operators helped us in creating our own unique_ptr. Here we will use move constructor & assignment operator to implement unsophisticated shared_ptr. Implementing Our shared_ptr with Move Constructor & Assignment Operator In some cases, we have a requirement where a single resource is represented by multiple pointers. We ...
You should probably return a reference here: T operator*(); Otherwise you are going to force a copy of the internal object as it is returned. Just like the operator-> this does not affect the state of the shared pointer so this is const.. In std::shared_ptr this is a noexcept operation.
shared_ptr; weak_ptr; They all are declared in a memory header file ... The copy constructor and the assignment operator of auto_ptr do not actually copy the stored pointer instead they transfer it, leaving the first auto_ptr object empty. ... in C++, Operator overloading is a compile-time polymorphism. It is an idea of giving special meaning ...
In this article, we will learn how to implement our own user defined shared pointer in C++. What is shared_ptr in C++? A std::shared_ptr is a container for raw pointers. It is a reference counting ownership model i.e. it maintains the reference count of its contained pointer in cooperation with all copies of the std::shared_ptr.
1. Overview. The C++11 std::shared_ptr<T> is a shared ownership smart pointer type. Several shared_ptr instances can share the management of an object's lifetime through a common control block.The managed object is deleted when the last owning shared_ptr is destroyed (or is made to point to another object). Memory management by shared_ptr is deterministic because the timing of a managed object ...
Return value. A reference to the idx-th element of the array, i.e., get [idx]. [] ExceptionThrows nothing. [] RemarkWhen T is not an array type, it is unspecified whether this function is declared. If the function is declared, it is unspecified what its return type is, except that the declaration (although not necessarily the definition) of the function is guaranteed to be legal.
Replaces the managed object with an object pointed to by ptr.Optional deleter d can be supplied, which is later used to destroy the new object when no shared_ptr objects own it. By default, delete expression is used as deleter. Proper delete expression corresponding to the supplied type is always selected, this is the reason why the function is implemented as template using a separate parameter Y.
rel_ops::operator!= rel_ops::operator> rel_ops::operator<= rel_ops::operator>=
shared_ptr<Type> var(new Type()); I wonder why they didn't allow a much simpler and better (imo): shared_ptr<Type> var = new Type(); Instead to achieve such functionality you need to use .reset(): shared_ptr<Type> var; var.reset(new Type()); I am used to OpenCV Ptr class that is a smart pointer that allows direct assignment and everything works ...
As for the copy and move-assignment operators, they too will be equivalent. Move-assignment will correctly transfer ownership and the copy-constructor will perform a shallow copy so that both shared_ptrs have ownership. If a shallow copy (shared ownership) is really want you want, then shared_ptr is the correct tool.