Stdenable Shared From This Function In C++

The std::enablesharedfromthis function is a utility function in C++ that enables an object to create a std::sharedptr instance of the very same object whose ownership it possesses. It is used to safely grab a reference over a sharedptr instance from within a class that is itself wrapped over a sharedptr.

Multiple shared_ptrs referring to the Same Object

When a std::sharedptr manages an object , it comes with a reference counting mechanism. This reference count will show how many sharedptr objects are pointing to one particular object. Once it reaches zero, it is destroyed in the context of garbage collection, the process of freeing space where the object is located.

Now, suppose you have an object that is already managed by a std:: made (i.e., the object was made or passed by std::makeshared). If you attempt to create a new sharedptr inside the object itself using std::this essentially means that when a shared_ptr<T> specifies this, the following occurs:

  • New sharedptr: Now, you create a new sharedptr object with a new reference count of its own.
  • Double reference counting: Now, there are two sharedptr objects pointing to the same object, although they both have their reference counts. It means that if one sharedptr class assumes that the object is still usable, the second shared_ptr class might delete it because it considers itself the only one managing it. It leads to undefined behavior and can result in a variety of problems:
  • Double deletion: When two shared_ptr instances are referencing this object and both of them are independent of each other, there will be a double delete error when both try to delete the object when their respective reference count drops down to zero.
  • Dangling pointers: The original sharedptr might think it still owns a valid object. However, if the new sharedptr destroys the object first, the original shared_ptr is left holding a dangling pointer, which can lead to runtime crashes or other undefined behavior.
  • Use of std::enable_shared_from_this method:

The std::enablesharedfromthis function is aimed at addressing this issue by making it possible to cause an object, which is already owned by sharedptr, to get another shared_ptr to the same object without the formation of another reference count.

When the object is first managed by a sharedptr (e.g., via std::the second, used when creating a sharedptr object through the template function template sharedptr(T* ptr): makeshared<T>(ptr)), the object stores a pointer to the control block that contains the reference counter.

When you use std::enablesharedfromthis, the class stores a weak pointer to the control block (which manages the reference counting for sharedptr), but not to the object itself. This weak pointer is created automatically when the first shared_ptr is made for the object, all handled behind the scenes.

The reason for using a weak pointer is to avoid increasing the reference count while still keeping track of the control block. This allows the object to safely create new shared_ptr instances pointing to itself later, without causing issues like double reference counting or double deletion.

As you know, when sharedfromthis is called, it checks this weak reference. If the weak reference is still alive (that means the object has not been destroyed), it will create a new sharedptr that shares the reference to the object of the original sharedptr object. The new object does not create a new reference count; instead, it makes the object's reference count the same as the object of the original type shared_ptr.

It helps to avoid double deletion, and as long as at least one shared_ptr exists, the object remains valid.

Example:

Let us take an example to illustrate the std::enablesharedfrom_this function in C++ .

Example

#include <iostream>
#include <memory>
#include <vector>
// Base class that uses enable_shared_from_this
class MyBase : public std::enable_shared_from_this<MyBase> {
public:
    // Constructor
    MyBase(const std::string& name) : name_(name) {
        std::cout << "MyBase constructor: " << name_ << std::endl;
    }
    // Destructor
    virtual ~MyBase() {
        std::cout << "MyBase destructor: " << name_ << std::endl;
    }
    // Function to get a shared_ptr to the current object
    std::shared_ptr<MyBase> getPtr() {
        return shared_from_this();
    }
    // Virtual Function to display information
    virtual void display() const {
        std::cout << "MyBase object: " << name_ << std::endl;
    }
protected:
    std::string name_;
};
// Derived class that also uses enable_shared_from_this
class MyDerived : public MyBase {
public:
    // Constructor
    MyDerived(const std::string& name, int value)
        : MyBase(name), value_(value) {
        std::cout << "MyDerived constructor: " << name_ << std::endl;
    }
    // Destructor
    ~MyDerived() override {
        std::cout << "MyDerived destructor: " << name_ << std::endl;
    }
    // Function to get a shared_ptr to the current object
    std::shared_ptr<MyDerived> getDerivedPtr() {
        return std::static_pointer_cast<MyDerived>(shared_from_this());
    }

    // Override display function
    void display() const override {
        std::cout << "MyDerived object: " << name_ << ", value: " << value_ << std::endl;
    }
private:
    int value_;
};
// Function to use shared_from_this in a callback-like scenario
void manipulateObject(const std::shared_ptr<MyBase>& basePtr) {
    std::cout << "Inside manipulateObject()" << std::endl;
    basePtr->display();
    // Get another shared_ptr using shared_from_this
    std::shared_ptr<MyBase> anotherPtr = basePtr->getPtr();
    std::cout << "Another pointer use_count(): " << anotherPtr.use_count() << std::endl;
}
//Function to test derived class with enable_shared_from_this
void testDerived() {
    std::cout << "\n=== Testing MyDerived class ===" << std::endl;
    // Create a shared_ptr to MyDerived
    std::shared_ptr<MyDerived> derivedPtr = std::make_shared<MyDerived>("DerivedObject", 42);
// Use the derived object
    derivedPtr->display();
    // Get another shared_ptr from within the object
    std::shared_ptr<MyDerived> derivedPtr2 = derivedPtr->getDerivedPtr();
    std::cout << "derivedPtr use_count(): " << derivedPtr.use_count() << std::endl;
    std::cout << "derivedPtr2 use_count(): " << derivedPtr2.use_count() << std::endl;
    // Manipulate object using a base class pointer
    manipulateObject(derivedPtr);
    std::cout << "After manipulateObject() use_count(): " << derivedPtr.use_count() << std::endl;
}
//Function to store shared pointers in a vector
void storeObjectsInVector(std::vector<std::shared_ptr<MyBase>>& vec) {
    std::cout << "\n=== Storing objects in vector ===" << std::endl;
    // Create and store multiple objects in the vector
    vec.push_back(std::make_shared<MyBase>("BaseObject1"));
    vec.push_back(std::make_shared<MyDerived>("DerivedObject1", 10));
    vec.push_back(std::make_shared<MyDerived>("DerivedObject2", 20)); 
    for (const auto& obj : vec) {
        obj->display();
    }
}
//Function to simulate complex interaction between objects
void complexInteraction() {
    std::cout << "\n=== Complex interaction ===" << std::endl;    
    // Create a base object
    std::shared_ptr<MyBase> basePtr = std::make_shared<MyBase>("ComplexBaseObject");
    // Create a derived object
    std::shared_ptr<MyDerived> derivedPtr = std::make_shared<MyDerived>("ComplexDerivedObject", 100);
    // Base pointer to a derived object
    std::shared_ptr<MyBase> baseFromDerived = derivedPtr;

    // Call manipulateObject on both base and derived objects
    manipulateObject(basePtr);
    manipulateObject(baseFromDerived);
    // Check reference counts
    std::cout << "basePtr use_count(): " << basePtr.use_count() << std::endl;
    std::cout << "derivedPtr use_count(): " << derivedPtr.use_count() << std::endl;
    std::cout << "baseFromDerived use_count(): " << baseFromDerived.use_count() << std::endl;
}
// Main Function to run all tests
int main() {
    // Testing derived class with enable_shared_from_this
    testDerived();
    // Storing objects in a vector and using enable_shared_from_this
    std::vector<std::shared_ptr<MyBase>> objectVector;
    storeObjectsInVector(objectVector);
    // Simulating complex interactions
    complexInteraction();
    return 0;
}

Output:

Output

=== Testing MyDerived class ===
MyBase constructor: DerivedObject
MyDerived constructor: DerivedObject
MyDerived object: DerivedObject, value: 42
derivedPtr use_count(): 2
derivedPtr2 use_count(): 2
Inside manipulateObject()
MyDerived object: DerivedObject, value: 42
Another pointer use_count(): 4
After manipulateObject() use_count(): 2
MyDerived destructor: DerivedObject
MyBase destructor: DerivedObject

=== Storing objects in vector ===
MyBase constructor: BaseObject1
MyBase constructor: DerivedObject1
MyDerived constructor: DerivedObject1
MyBase constructor: DerivedObject2
MyDerived constructor: DerivedObject2
MyBase object: BaseObject1
MyDerived object: DerivedObject1, value: 10
MyDerived object: DerivedObject2, value: 20
=== Complex interaction ===
MyBase constructor: ComplexBaseObject
MyBase constructor: ComplexDerivedObject
MyDerived constructor: ComplexDerivedObject
Inside manipulateObject()
MyBase object: ComplexBaseObject
Another pointer use_count(): 2
Inside manipulateObject()
MyDerived object: ComplexDerivedObject, value: 100
Another pointer use_count(): 3
basePtr use_count(): 1
derivedPtr use_count(): 2
baseFromDerived use_count(): 2
MyDerived destructor: ComplexDerivedObject
MyBase destructor: ComplexDerivedObject
MyBase destructor: ComplexBaseObject
MyBase destructor: BaseObject1
MyDerived destructor: DerivedObject1
MyBase destructor: DerivedObject1
MyDerived destructor: DerivedObject2
MyBase destructor: DerivedObject2

Explanation:

The code demonstrates the use of std::enablesharedfromthis function to safely manage object ownership via std::sharedptr in a class hierarchy. The base class, MyBase, inherits from std::enablesharedfromthis<MyBase>, which allows it to create sharedptr instances of itself using sharedfromthis. The derived class, MyDerived, extends MyBase and also utilizes sharedfromthis to return shared_ptr instances cast to its type.

Key functions like getPtr and getDerivedPtr ensure objects that can safely return sharedptr instances without creating separate reference counts. The manipulateObject function shows how sharedfromthis is used in a callback scenario to maintain proper reference counting. The testDerived function tests the safe use of sharedfrom_this in derived classes, while storeObjectsInVector manages multiple objects in a vector.

Lastly, the complexInteraction function explores polymorphic behavior between the base and derived classes, ensuring proper object ownership and destruction. The code highlights how sharedfromthis prevents issues like double deletion by making sure all shared_ptr instances share the same reference count, guaranteeing safe object lifetime management.

Complexity Analysis:

Time Complexity:

  • Object creation (std::make_shared): Constant time, O(1), for each object because memory allocation and reference count initialization are done once per object.
  • Reference counting: Incrementing or decrementing the reference count of a shared_ptr is O(1) for each pointer operation.
  • Sharedfromthis: This function retrieves the shared_ptr from an existing object and adds a new reference to the control block, which is O(1).
  • Vector operations (storeObjectsInVector): Inserting objects into a std::vector is O(1) on average unless a reallocation occurs, in which case it is O(n), where n is the current size of the vector.
  • Space Complexity:

  • Object memory: Each object (base or derived) requires O(1) space complexity for the object itself, and O(1) space complexity for the control block managed by shared_ptr.
  • Vector storage: Storing n objects in a vector takes O(n) space complexity for objeccpp tutorialers and associated reference counting control blocks.

Input Required

This code uses input(). Please provide values below: