Safe Shared Pointer Acquisition from Member Functions Using CRTP
Asynchronous programming frequently requires registering an object for callbacks. Passing the raw this pointer directly risks undefined behavior if the callback executes after object destruction. While std::shared_ptr manages lifetime automatically, constructing it directly from this creates independent control blocks, leading to multiple deletions when the object is already managed by another shared_ptr.
The std::enable_shared_from_this template employs the Curiously Recurring Template Pattern (CRTP) too solve this. It embeds a weak reference to the object's control block, allowing safe acquisition of additional shared_ptr instances that share ownership with existing ones.
Consider the problematic approach:
class Resource {
public:
Resource() { std::cout << "Resource acquired\n"; }
~Resource() { std::cout << "Resource released\n"; }
std::shared_ptr<Resource> getPtr() {
return std::shared_ptr<Resource>(this); // Dangerous!
}
};
void demonstrateFailure() {
Resource* raw = new Resource();
std::shared_ptr<Resource> owner1(raw);
std::cout << "owner1 count: " << owner1.use_count() << "\n";
std::shared_ptr<Resource> owner2(raw); // Separate control block
std::cout << "owner2 count: " << owner2.use_count() << "\n";
std::shared_ptr<Resource> owner3 = raw->getPtr(); // Third control block
std::cout << "owner3 count: " << owner3.use_count() << "\n";
// Three separate counts, destructor called three times = UB
}
Each initialization from the raw pointer creates a distinct control block. When these shared_ptr instances go out of scope, they all attempt to delete the same memory.
The CRTP-based solution:
class SharedResource : public std::enable_shared_from_this<SharedResource> {
public:
SharedResource() { std::cout << "Resource acquired\n"; }
~SharedResource() { std::cout << "Resource released\n"; }
void registerCallback(Executor& exec) {
// Capture weak_ptr to avoid extending lifetime unnecessarily
exec.post([weak = weak_from_this()] {
if (auto shared = weak.lock()) {
shared->onCompletion();
}
});
}
std::shared_ptr<SharedResource> safeGetPtr() {
return shared_from_this(); // Shares existing control block
}
};
void demonstrateSafety() {
auto resource = std::make_shared<SharedResource>();
std::cout << "Initial count: " << resource.use_count() << "\n";
auto another = resource->safeGetPtr();
std::cout << "After safeGetPtr: " << resource.use_count() << "\n";
auto third = another; // Copy increases count
std::cout << "After copy: " << resource.use_count() << "\n";
// Single destructor call when all references drop
}
The inheritance from std::enable_shared_from_this<SharedResource> establishes the necessary internal weak pointer during the first shared_ptr construction. Subsequent calls to shared_from_this() or weak_from_this() retrieve this established control block, ensuring reference counts remain consistent across all shared_ptr instances referencing the object.