Stdenable Shared From This Function In C++ - C++ Programming Tutorial
C++ Course / Functions / Stdenable Shared From This Function In C++

Stdenable Shared From This Function In C++

BLUF: Mastering Stdenable Shared From This Function In C++ is a critical step in becoming a proficient C++ developer. This lesson provides a deep dive into the syntax, performance considerations, and real-world applications of this concept.
Key Performance Insight: Stdenable Shared From This Function In C++

C++ is renowned for its efficiency. Learn how Stdenable Shared From This Function In C++ enables low-level control and high-performance computing in the tutorial below.

The std::enablesharedfromthis method is a convenient utility in C++ that allows an object to generate a std::sharedptr object of itself, of which it holds ownership. This functionality is employed to securely obtain a reference to a sharedptr object from inside a class that is encapsulated within a sharedptr.

Multiple shared_ptrs referring to the Same Object

When a std::sharedptr handles an object, it includes a reference counting system. This count indicates the number of sharedptr instances pointing to a specific object. When this count hits zero, the object is then deleted within the garbage collection context, which deals with reclaiming memory space allocated for the object.

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 method is designed to tackle this problem by enabling an object, already possessed by sharedptr, to obtain an additional shared_ptr pointing to the same object without incrementing the reference count.

When an object is initially handled by a sharedptr (for example, through std::the next, employed when generating a sharedptr object via the template function template sharedptr(T* ptr): makeshared<T>(ptr)), it retains a pointer to the control block housing the reference count.

When utilizing std::enablesharedfromthis, the class retains a weak pointer to the control block (which oversees the reference counting for sharedptr), rather than to the object directly. This weak pointer is generated automatically upon the creation of the first shared_ptr for the object, all managed internally.

Utilizing a weak pointer is essential to prevent increments in the reference count while maintaining visibility of the control block. By doing so, the object can securely generate additional shared_ptr instances that refer back to itself in the future, thereby preventing problems such as redundant reference counting or premature deletion.

When invoking sharedfromthis, it verifies the weak reference associated with it. If the weak reference remains valid (indicating the object's existence), a new sharedptr is generated to reference the object held by the initial sharedptr. This new instance does not increment the reference count separately; rather, it synchronizes the reference count with the original shared_ptr object's reference count.

It assists in preventing duplicate deletion, and as long as there is at least one shared_ptr in existence, the object retains its validity.

Example:

Let's consider a scenario to demonstrate the std::enablesharedfrom_this method 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 showcases the implementation of std::enablesharedfromthis method to securely handle object ownership through std::sharedptr within a class hierarchy. The parent class, MyBase, inherits from std::enablesharedfromthis<MyBase>, enabling it to generate sharedptr objects of itself using sharedfromthis. The child class, MyDerived, expands MyBase and similarly employs sharedfromthis to provide shared_ptr instances converted to its specific type.

Key methods such as retrievePointer and retrieveDerivedPointer guarantee that objects can provide sharedptr instances without introducing additional reference counts. The adjustEntity method illustrates the application of sharedfromthis in a callback situation to uphold correct reference tracking. The validateDerived method assesses the secure application of sharedfrom_this in subclasses, whereas organizeEntitiesInVector oversees the handling of numerous objects within a vector.

Finally, the complexInteraction method examines the polymorphic behavior exhibited by the base and derived classes, confirming correct object ownership and deletion. This code demonstrates the significance of sharedfromthis in preventing problems such as double deletion, as it ensures that all shared_ptr objects maintain the same reference count, thus ensuring secure management of object lifetimes.

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:

Logic Practice
Install Logic Practice
Add to home screen for a faster app-like experience