0

When using std::shared_ptr it can often be useful to make use of std::enable_shared_from_this<T> so that you have access to the shared_from_this() function.

One requirement of using shared_from_this() is that all instances of the object are constructed using std::shared_ptr. While this is a perfectly fine requirement, it feels very difficult to enforce to future users of the class.

If I create an object:

class MyClass : public std::enable_shared_from_this<MyClass>
{
public:
    MyClass()
    {

    }

    void doAThing()
    {
        // something I need done asynchronously
    }

    void someFunction()
    {
        std::weak_ptr<MyClass> w (shared_from_this());

        // we need to use a lambda that is executed asynchronously and
        // so we pass the std::weak_ptr to it to check this object still exists it is executed
        std::function<void()> f = [w]()
        {
            if (! w.expired())
                w.lock()->doAThing();
        };


        callAsynchronously (f); // this function passes the lambda to some queue and executes it asynchronously
    }
};

and then someone - perhaps years later - uses this class without constructing it as a shared_ptr...

MyClass m;
m.someFunction();

then we get a runtime crash:

libc++abi.dylib: terminating with uncaught exception of type std::__1::bad_weak_ptr: bad_weak_ptr

To be clear, I understand the solution to this is:

std::shared_ptr<MyClass> m = std::make_shared<MyClass>();
m->someFunction();

(of course one would need to ensure that the shared_ptr existed long enough for the asynchronous callback to execute, but I'm ignoring that here).

My question is this - how can we add some kind of static assertion in the constructor of an object that inherits from std::enable_shared_from_this<T> so that any construction of that object not as a std::shared_ptr is picked up at compile time rather than run time?

Adam Stark
  • 422
  • 1
  • 5
  • 12
  • 3
    You can make `MyClass` constructor private and add static function returning `std::shared_ptr` – fas May 06 '20 at 08:20
  • I tried that but I get a bad access when I try to use `shared_from_this()` (unless I'm doing it wrong). Also, it'd be nice if I could continue to use a normal constructor if possible. – Adam Stark May 06 '20 at 08:29
  • @AdamStark I don't think it is possible since the constructor called by `make_shared` or `shraer_ptr::shared_ptr` has no knowledge that it is called by these functions. – Daniel Langr May 06 '20 at 08:38
  • Yes, I feared that may be the answer. I was hoping for some thing like this: `static_assert (std::is_shared_ptr (this), "Please use a shared_ptr!");` - but maybe it doesn't exist... – Adam Stark May 06 '20 at 08:48
  • If it doesn't exist, something like it would be a good addition to stop a whole category of runtime bugs. Even better if it could be put into `std::enable_shared_from_this` and then no-one would be able to construct the object in the wrong way – Adam Stark May 06 '20 at 08:49
  • You can only have the private constructor in the most derived class, so it couldn't go in `std::enable_shared_from_this` – Caleth May 06 '20 at 09:11
  • I didn't mean a private constructor - just that any static assertion that may be put into the std library in future could live in there somehow so that any future programmers using `std::enable_shared_from_this` would automatically have that functionality (if it is possible). Just speculating... – Adam Stark May 06 '20 at 10:05
  • 1
    There is no nice way. A private ctor is problematic, as it means you can't use it from a derived class, and even for a final class, you can't use `make_shared`. – curiousguy May 09 '20 at 09:07
  • Agreed - it feels like an oversight that there isn't a way to enforce shared_ptr use at compile time. – Adam Stark May 10 '20 at 10:21

1 Answers1

1

The following code with a create-function worked for me without exception.

#include <memory>

class X : public std::enable_shared_from_this<X> {
    private:
        X() = default;

    public:
        static std::shared_ptr<X> makeX() {
            return std::shared_ptr<X>(new X());
        }

        void doSth() {
            auto sharedPtr = shared_from_this();
            // do sth
        }
};

int main() {
    auto x = X::makeX();
    x->doSth();
}
DasElias
  • 569
  • 8
  • 19