Wednesday, March 12, 2025

shared_ptr

Overview
shared_ptr are useful when a resource such as a piece of memory or a file handle can be collectively shared by multiple owners.

Details
Sometimes a resource needs to be shared across without duplication. An unique_ptr discussed above cannot be used in such cases.
For example, a chat server needs to share a message its clients without duplication.
shared_ptr addresses this issue. Unlike a unique_ptr, the underlying resource is collectively owned by a set of shared_ptrs. The underlying resource is released when the last instance of shared_ptr goes out of scope.

shared_ptr
Syntax
template <class T> class shared_ptr

member types
NameDescription
element_typefirst template parameter (T). The type of the managed object

Constructors
Commonly used constructors. Some support custom allocators.
NameDescription
shared_ptr()default constructor. use_count() is 0.
Example:
shared_ptr<int>()
shared_ptr
(nullptr_t)
same as default constructor.
Example:
shared_ptr<int>(nullptr)
template <class U>
shared_ptr
(U* p)
takes ownership of p and use default deleter. use_count() is 1.
Example:
shared_ptr<int>(new int)
template <class U, class D> shared_ptr
(U* p, D del)
takes ownership of p and use deleter del. use_count() is 1.
Example:
auto d = default_delete<int>();
shared_ptr<int>  (new int, d);
template <class D
shared_ptr 
(nullptr_t,  D del)
same as default but uses deleter del. use_count() is 0.
Example:
auto d = default_delete<int>();
shared_ptr<int>  (nullptr, d);
shared_ptr
(const shared_ptr& x)
copy constructor. shares pointer and deleter from x. increments use_count().
Example:
auto d = default_delete<int>();
shared_ptr<int>  x(new int, d); 
shared_ptr<int> (x);
template <class U
shared_ptr
(
const shared_ptr<U>& x)
copy constructor. shares pointer and deleter from x. 
Example:
auto d = default_delete<int>();
shared_ptr<int>  (shared_ptr<int> (new int, d));
shared_ptr
(shared_ptr&& x)
move constructor. move pointer and deleter from x. x becomes empty.
Example:
shared_ptr<int>  x(new int, default_delete<int>());
shared_ptr<int>  (move(x))

template <class U
shared_ptr
(const shared_ptr<U>&& x)
move constructor. move pointer and deleter from x. x becomes empty.
Example:
auto d = default_delete<int>();
shared_ptr<int>(shared_ptr<int> (new int, d));
template <class U
shared_ptr
(const weak_ptr<U>& x)
same as copy constructor. exception on thrown if x has expired.
Example:
shared_ptr<int>  x (new int);
weak_ptr<int>  w(x);
shared_ptr<int>  y (w);
template <class U
shared_ptr
(auto_ptr<U>&& x)
takes ownership from x. uses default deleter and  use_count() is 1.
Example:
shared_ptr<int>  (auto_ptr<int> (new int));
template <class U, class D
shared_ptr
(unique_ptr<T,D>&& x)
takes ownership from x and  use_count() is 1.
Example:
shared_ptr<int>  (unique_ptr<int> (new int, default_delete<int>()));
template <class U
shared_ptr
(const shared_ptr<U>& x, 
element_type *p)
Unlike shared_ptr constructors discussed above, an aliasing  constructor co owns internal member object of shared_ptr  x. It also increments its use_count().  
In the example below, the owned object will be share_ptr x and the stored object will be x->data. In this case the get() will return stored object.
Example:
struct S {int data;};
shared_ptr<S>  x(new S);
shared_ptr<int> (x, x->data);
The example 5 demonstrates it. 

Methods
The following describes functionality in detail.
NameDescription
element_type* get()Returns the pointer of the resource it's holding without relinquishing the ownership.
Example
    auto sp{shared_ptr<int>(new int(10))};
    //prints 10
    cout << *sp.get() << endl;
bool unique()Checks if use_count() is 1.
Example
    cout << boolalpha;
    auto sp{shared_ptr<int>(new int(10))};
    //prints true
    cout << sp.unique() << endl;
    //prints false
    cout << shared_ptr<int>(sp).unique() << endl;
long int use_count()Returns number of instances of shared_ptr sharing the resource.
Example
    auto sp{shared_ptr<int>(new int(10))};
    //prints 1
    cout << sp.use_count() << endl;
    //prints 2
    cout << shared_ptr<int>(sp).use_count() << endl;
  1. template <class U>
      bool owner_before
    (const shared_ptr<U>& x)
  2. template <class U>
    bool owner_before
    (const weak_ptr<U>& x)
Applies to alias based constructs. When two shared_ptrs or weak_ptrs are compared using operator <,  value based comparison is applied which compares the stored objects.  Whereas owner_before() uses owner_based comparison that compares the owner share_ptr objects instead.  Two of these aliased shared_ptr or weak_ptr are considered equivalent (i.e., this function returns false no matter the order of the operands) if they both share ownership, or they are both empty.

Example
     cout << boolalpha;
    //1
    auto sp{shared_ptr<int>{new int{10}}};
    auto sp2{shared_ptr<int>{sp,new int {20}}};
    auto sp3{shared_ptr<int>{sp,new int {30}}};


    //prints *sp2 = 20 *sp3 = 30
    cout << "*sp2 = " << *sp2 << " *sp3 = " << *sp3 << endl;
    
    //prints true
    cout <<  (sp2 < sp3) << endl;
    
    //prints false
    cout << sp2.owner_before(sp3) << endl;
    cout << sp3.owner_before(sp2) << endl;
    cout << endl;

    //2
    auto wp2{weak_ptr<int>{sp2}};
    auto wp3{weak_ptr<int>{sp3}};

    //prints false
    cout << wp2.owner_before(wp3) << endl;
    //prints false
    cout << wp3.owner_before(wp2) << endl;
  1. template <class U>
    void reset (U* p)
  2. template <class U, class D>
    void reset (U* p, D del)
  3. void reset()
Destroys the resource it's currently holding by calling deleter and releases the resource. Optionally new resource can be set to it.
  1. Replaces with a new value which is compatible with its type T.
  2. Same as 1 and also accepts new deleter
  3. Becomes empty
Example
    struct product{int id;};

    template<>
    struct std::default_delete<product> 
    {
        void operator()(product *p)
        {
            cout << "deleting product id: " << p->id << endl;
            delete p;
        }
    };

    shared_ptr<product> sp;
    
    //1
    sp.reset(new product(20));

    //2
    sp.reset(new product(30),default_delete<product>());

    //prints:deleting product id: 30
    //3
    sp.reset();
operator bool()Can be used to check if the use_count() is 1 or more.
Example
    shared_ptr<int> sp;
    cout << boolalpha;
    
    //prints false
    cout << (bool)sp << endl;
    sp.reset(new int);
    
    //prints true
    cout << (bool)sp << endl;
element_type& operator *()Object dereferencing.
Example
    shared_ptr<int> sp(new int(20));
    //prints 20
    cout << *sp;
element_type* operator ->()Member dereferencing.
Example
    shared_ptr<pair<int,int>> sp(new pair<int,int> {10,20});
    //prints 20
    cout << sp->second;
element_type& operator []
(size_t index)
Array accessing index operator. Available only in specialized constructs.
Example
    shared_ptr<int[]> sp(new int[10]);
    sp[5]=20;
    //prints 20
    cout << sp[5];
  1. shared_ptr& operator=
    (const shared_ptr& x) 
  2. template <class U>
    shared_ptr& operator=
    (const shared_ptr<U>& x)
  3. shared_ptr& operator=
    (shared_ptr&& x)
  4. template <class U>
    shared_ptr& operator=
    (shared_ptr<U>&& x)
  5. template <class U>
    shared_ptr& operator=
    (auto_ptr<U>&& x)
  6. template <class U, class D>
    shared_ptr& operator=
    (unique_ptr<U,D>&& x)
Assignment Operator.
Example
    shared_ptr<int> sp (new int(10));
    shared_ptr<int> sp2;

    //1
    sp2=sp;
    //prints sp = 10 sp2= 10
    cout << "sp = " << *sp << " sp2= " << *sp2 << endl;

    //2
    sp2 = shared_ptr<int>(new int(20));
    //prints sp2= 20
    cout << "sp2= " << *sp2 << endl;

    //3
    sp2 = move(sp);
    //prints sp2= 10
    cout << "sp2= " << *sp2 << endl;
    
    //4
    sp2 = move(shared_ptr<int>(new int(30)));
    //prints sp2= 30
    cout << "sp2= " << *sp2 << endl;

    //6
    sp2 = move(unique_ptr<int>(new int(40), default_delete<int>()));
    //prints sp2= 40
    cout << "sp2= " << *sp2 << endl;

The example 6 demonstrates it. 

External Methods
The following describes relational operators.
NameDescription
  1. template <class T, class U>
    bool operator ==
    (const shared_ptr<T>& lhs,
    const shared_ptr<U>& rhs)
  2. template <class T>
    bool operator ==
    (const shared_ptr<T>& lhs, nullptr_t)
  3. template <class T>
    bool operator ==
    (nullptr_tconst shared_ptr<T>& rhs) 
Equality operator.
  1. template <class T, class U>
    bool operator !=
    (const shared_ptr<T>& lhs,
    const shared_ptr<U>& rhs)
  2. template <class T>
    bool operator !=
    (const shared_ptr<T>& lhs, nullptr_t)
  3. template <class T>
    bool operator !=
    (nullptr_tconst shared_ptr<T>& rhs) 
Inequality operator.
  1. template <class T, class U>
    bool operator >
    (const shared_ptr<T>& lhs,
    const shared_ptr<U>& rhs)
  2. template <class T>
    bool operator >
    (const shared_ptr<T>& lhs, nullptr_t)
  3. template <class T>
    bool operator >
    (nullptr_tconst shared_ptr<T>& rhs) 
Greater than operator.
  1. template <class T, class U>
    bool operator >=
    (const shared_ptr<T>& lhs,
    const shared_ptr<U>& rhs)
  2. template <class T>
    bool operator >=
    (const shared_ptr<T>& lhs, nullptr_t)
  3. template <class T>
    bool operator >=
    (nullptr_tconst shared_ptr<T>& rhs) 
Greater than or equal to operator.
  1. template <class T, class U>
    bool operator <
    (const shared_ptr<T>& lhs,
    const shared_ptr<U>& rhs)
  2. template <class T>
    bool operator <
    (const shared_ptr<T>& lhs, nullptr_t)
  3. template <class T>
    bool operator <
    (nullptr_tconst shared_ptr<T>& rhs) 
Less than operator.
  1. template <class T, class U>
    bool operator <=
    (const shared_ptr<T>& lhs,
    const shared_ptr<U>& rhs)
  2. template <class T>
    bool operator <=
    (const shared_ptr<T>& lhs, nullptr_t)
  3. template <class T>
    bool operator <=
    (nullptr_tconst shared_ptr<T>& rhs) 
Less than or equal to operator.

The following describes ostream operator.
NameDescription
template <class charT, class traits, class T>
basic_ostream<charT,traits>& operator<< 
(basic_ostream<charT,traits>& os,
const shared_ptr<T>& x)
Insert  stored pointer into output stream.

Example
    //prints 0x33f81f80
    cout << make_shared<int>(10);    
    return 0;

make_shared()
make_shared() template function enables creation of shared_ptr and initializing it by passing by passing argument list.
Syntax 
template< class T, class... Args >
shared_ptr<T> make_shared( Args&&... args );

Example
    auto sp = make_shared<pair<int,int>>(10,20);
    //prints 10  20
    cout << sp->first << "  " << sp->second << endl;
The example 7 demonstrates its usage.

allocate_shared()
Similar to make_shared()allocate_shared() template function enables creation of shared_ptr and initializing it by passing by passing argument list. However it uses a custom allocator. 

Syntax 
template <class T, class Alloc, class... Args>  
shared_ptr<T> allocate_shared (const Alloc& alloc, Args&&... args);

Example
    allocator<int> alloc;
    auto sp = allocate_shared<int> (alloc,10);
The example 8 demonstrates its usage.

shared pointer casting()
These are similar to cast functions such as staticdynamic and const except applies to resource owned by a shared_ptr

Syntax  
template <class T, class U>  shared_ptr<T> static_pointer_cast (const shared_ptr<U>& sp)
template <class T, class U>  shared_ptr<T> dynamic_pointer_cast (const shared_ptr<U>& sp)
template <class T, class U>  shared_ptr<T> const_pointer_cast (const shared_ptr<U>& sp)
When applied it creates  new shared_ptr<T> object after applying cast on shared_ptr<U. The use_count() is incremented. 

Example
    struct base 
    {
        void print() {cout << "base" << endl;}
        //needed for dynamic_cast
        virtual ~base(){}
    };
    
    struct derived:public base 
    {
        //const needed for const cast
void print() const {cout << "derived" << endl;} //needed for dynamic cast virtual ~derived(){} } d; auto spd = make_shared<derived>(d); auto spb = static_pointer_cast<base>(spd); //prints 2 cout << spb.use_count() << endl; //prints base spb->print(); auto spd2 = dynamic_pointer_cast<derived>(spb); //prints 3 cout << spd2.use_count() << endl; //prints derived spd2->print(); auto spd3 = const_pointer_cast<const derived>(spd2); //prints 4 cout << spd3.use_count() << endl; //prints derived spd3->print();
The example 9 demonstrates its usage.

owner_less
As discussed earlier, aliasing constructs and owner_before() of use owner based comparison. owner_less define functor objects that use owner based comparison while comparing shared_ptr or weak_ptr.
This is a replacement for less (or operator<) to be used for these types when sorting needs to be based on their owned pointer instead of their stored pointer (which is what is compared by operator<).

Syntax 
//Ptr The type of the managed pointers to be ordered according to owned resource, aliased as member types first_argument_type and second_argument_type.
template <class Ptr> 
struct owner_less

//T The type of object pointed by the managed pointer type.

//specialization of shared_ptr
template <class T>
struct owner_less<shared_ptr<T>>
//specialization of weak_ptr
template <class T> struct owner_less<weak_ptr<T>>

member types
NameDescription
result_type bool indicating result of the operation.
first_argument_typefirst template parameter (Ptr). This can be either shared_ptr<T> or weak_ptr<T>.
second_argument_typesecond template parameter (Ptr). This can be either shared_ptr<T> or weak_ptr<T>.

methods
The comparison is provided by overloaded operator () function. Returns true if the first argument is considered to go before the second argument in a strict weak ordering based on their owned pointers.
The function uses shared_ptr::owner_before and/or weak_ptr::owner_before to determine this order.
NameDescription
bool operator()
(const shared_ptr<T>& lhs,
 const shared_ptr<T>& rhs ) 
specialization of shared_ptr
bool operator()
(const weak_ptr<T>& lhs,
 const weak_ptr<T>& rhs )
specialization of weak_ptr
  1. bool operator()
    (const shared_ptr<T>& lhs,
    const weak_ptr<T>& rhs )
  2. bool operator()
    (const weak_ptr<T>& lhs,
    const shared_ptr<T>& rhs )
specialization of shared_ptr and weak_ptr.
The example 10 demonstrates its usage.

enable_shared_from_this
Consider the following scenario.
  1. An instance of shared_ptr x is created from class C. 
  2. class C defines a method getptr() that returns  shared_ptr from self. 
  3. Another instance of shared_ptr y is created from x.
    struct C
    {
        shared_ptr<C> getptr() { return shared_ptr<C>(this); }
    };

    auto x = make_shared<C>();
    //prints 1
    cout << x.use_count();

    auto y = x->getptr();    
    //prints 1
    cout << y.use_count();

Even though it's legal, it has a side effect. The 
use_count() of  shared_ptr x is not incremented. This leads to unexpected situations such as double delete leading to crash.
This is depicted in example 11.

To get around this issue, enable_shared_from_this class is introduced. All the classes that intend to create a  shared_ptr from self should derive from this class.
syntax
template< class T > class enable_shared_from_this

Constructors
Commonly used constructors. Some support custom allocators.
NameDescription
enable_shared_from_this()default constructor. 
Example
    struct C:public enable_shared_from_this<C>
    {
        shared_ptr<C> getptr() { return shared_from_this(); }
    };
enable_shared_from_this (const enable_shared_from_this&)
copy constructor.

Methods
It provides a single method, shared_from_this() to return a shared_ptr with use_count() increment.
NameDescription
shared_ptr <T> shared_from_this()Constructs and returns a shared_ptr  object pointing to *this and sharing ownership with existing shared_ptr objects.

Example
    struct C:public enable_shared_from_this<C>
    {
        shared_ptr<C> getptr() { return shared_from_this(); }
    };

    auto x = make_shared<C>();
    //prints 1
    cout << x.use_count();

    auto y = x->getptr();    
    //prints 2
    cout << y.use_count();
The example 12 demonstrates its usage.

Usage Example

linked list
As implemented using unique_ptr, a double linked list can  also be implemented using shared_ptr .
The same strategy also implies here to avoid double referencing as shown below.
struct doublelist
{
    struct node;
    using LLNODE = shared_ptr<node>;
    using LLNODEptr = shared_ptr<node>*;
struct node { T data; LLNODEptr prev; LLNODE next; }; LLNODE head; LLNODEptr tail; }
The example 13 depicts a solution. 

linked list
A chat server needs to share a message with its clients without duplication. 
The solution is to use shared_ptr for sharing the message across the clients.
This  is implemented in the  example 14
As seen in its console output, a chaat server publishes a message with its 3 clients. Instead of duplicating the message, it's forwarded as shared_ptr  to the clients. After all the instances of shared pointer are destroyed, deleter is called.

No comments:

Post a Comment