Home > Blockchain >  Encounter std::bad_weak_ptr exception after converting a unique_ptr created from a factory method to
Encounter std::bad_weak_ptr exception after converting a unique_ptr created from a factory method to

Time:03-15

In summary, I have a class inherited from std::enabled_shared_from_this, and there is a factory method return an std::unique_ptr of it. In another class, I convert the std::unique_ptr of the previous class object to std::shared_ptr, and then I call shared_from_this(), which then throws std::bad_weak_ptr. The code is shown below:

#include <memory>
#include <iostream>

struct Executor;
struct Executor1 {
  Executor1(const std::shared_ptr<Executor>& executor,
            int x): parent(executor) {
    std::cout << x << std::endl;
  }
  std::shared_ptr<Executor> parent;
};

struct Backend {
  virtual ~Backend() {}
  virtual void run() = 0;
};

struct Executor: public Backend, public std::enable_shared_from_this<Executor> {
  const int data = 10;
  virtual void run() override {
    Executor1 x(shared_from_this(), data);
  }
};

// std::shared_ptr<Backend> createBackend() {
std::unique_ptr<Backend> createBackend() {
  return std::make_unique<Executor>();
}

class MainInstance {
private:
  std::shared_ptr<Backend> backend;
public:
  MainInstance(): backend(createBackend()) {
    backend->run();
  }
};

int main() {
  MainInstance m;
  return 0;
}

Indeed changing std::unique_ptr<Backend> createBackend() to std::shared_ptr<Backend> createBackend() can solve the problem, but as I understand, in general, the factory pattern should prefer return a unique_ptr. Considering a good pratice of software engineering, is there a better solution?

CodePudding user response:

[util.smartptr.shared.const]/1 In the constructor definitions below, enables shared_from_this with p, for a pointer p of type Y*, means that if Y has an unambiguous and accessible base class that is a specialization of enable_shared_from_this (23.11.2.5), then [magic happens that makes shared_from_this() work for *p - IT]

template <class Y, class D> shared_ptr(unique_ptr<Y, D>&& r);

[util.smartptr.shared.const]/29 Effects: ... equivalent to shared_ptr(r.release(), r.get_deleter())...

template<class Y, class D> shared_ptr(Y* p, D d);

[util.smartptr.shared.const]/10 Effects: ... enable shared_from_this with p

Your example executes std::shared_ptr<Backend>(uptr) where uptr is std::unique_ptr<Backend>, which is equivalent to std::shared_ptr<Backend>(p, d) where p is of type Backend*. This constructor enables shared_from_this with p - but that's a no-op, as Backend doesn't have an unambiguous and accessible base class that is a specialization of enable_shared_from_this

In order for Executor::enable_from_this to work, you need to pass to a shared_ptr constructor a pointer whose static type is Executor* (or some type derived therefrom).

CodePudding user response:

Ok, I find a simple solution, that is, using auto as the return type of the factory function, instead of std::unique_ptr or std::shared_ptr, and keeping std::make_unique inside the factory function. The factory function createBackend should be:

auto createBackend() {
  return std::make_unique<Executor>();
}

In this case, the return type can be automatically determined, although I don't know how it works exactly. This code can return either unique_ptr or shared_ptr, which should be better than just using shared_ptr. I tested clang and gcc, and both of them worked, but I am still not sure if this is gauranteed by the type deduction and the implicit conversion.

  • Related