How can I use std::enable_shared_from_this in both super and subclass?
Asked Answered
D

3

7

I have two classes A and B, where B is a subclass of A. I need both classes to use std::enable_shared_from_this.

I tried this:

#include <memory>
#include <iostream>
#include <vector>


class A : public std::enable_shared_from_this<A> {
  public:
    void insertme(std::vector<std::shared_ptr<A>>& v) {
        std::cout << "A::insertme\n";
        v.push_back(shared_from_this());
        std::cout << "OK\n";
    }
};

class B : public A, public std::enable_shared_from_this<B> {
  public:
    void insertme(std::vector<std::shared_ptr<B>>& v) {
        std::cout << "B::insertme\n";
        v.push_back(std::enable_shared_from_this<B>::shared_from_this());
        std::cout << "OK\n";
    }
};

int main()
{
    std::vector<std::shared_ptr<A>> va;
    std::vector<std::shared_ptr<B>> vb;

    std::shared_ptr<A> pa = std::make_shared<A>();
    std::shared_ptr<B> pb = std::make_shared<B>();

    pa->insertme(va);
    pb->insertme(vb);
}

(In order to avoid that shared_from_this() be ambiguous, I had to fully qualify it in B::insertme.)

When I run the above program, I get this output:

A::insertme
OK
B::insertme
terminate called after throwing an instance of 'std::bad_weak_ptr'
  what():  bad_weak_ptr
Aborted (core dumped)

So A::insertme works, but B::insertme does not.

I'm using GCC 9.1.0 under Linux.

What am I doing wrong?

Daggerboard answered 17/3, 2020 at 19:55 Comment(0)
K
9

You only need to (and only can) inherit from shared_from_this in the base class:

class A : public std::enable_shared_from_this<A> {
  public:
    void insertme(std::vector<std::shared_ptr<A>>& v) {
        std::cout << "A::insertme\n";
        v.push_back(shared_from_this());
        std::cout << "OK\n";
    }
};

class B : public A {
  public:
    void insertme(std::vector<std::shared_ptr<B>>& v) {
        std::cout << "B::insertme\n";
        v.push_back(std::static_pointer_cast<B>(shared_from_this()));
        std::cout << "OK\n";
    }
};

This means you need the explicit static_pointer_cast to get a shared_ptr<B>, but you could wrap that into an override in B if you want:

std::shared_ptr<B> shared_from_this() { return std::static_pointer_cast<B>(A::shared_from_this()); }
Klement answered 17/3, 2020 at 20:7 Comment(1)
Thank you. I've accepted your answer, but with the caveat that I cannot find static_pointer_cast as a member function of std::shared_ptr. It does, however, exist as a non-member function: std::static_pointer_cast<B>(A::shared_from_this())Daggerboard
T
2

The automatic linkage to enable_shared_from_this<X> that gets set up when a shared_ptr<T> is created only works if the class type T inherits exactly one unambiguous public enable_shared_from_this base. But B inherits two different enable_shared_from_this bases.

Instead, you can have just the enable_shared_from_this<A>, and write a custom B::shared_from_this() which makes use of A::shared_from_this():

class B : public A {
  public:
    // These hide the A::shared_from_this(), but they can still be
    // used by qualification if wanted.
    std::shared_ptr<B> shared_from_this()
    { return std::static_pointer_cast<B>(A::shared_from_this()); }
    std::shared_ptr<const B> shared_from_this() const
    { return std::static_pointer_cast<const B>(A::shared_from_this()); }

    // ...
};
Towe answered 17/3, 2020 at 20:9 Comment(2)
"only works if the class type T inherits exactly one unambiguous public enable_shared_from_this base" note that here all base classes are public and unambiguousHawsehole
True. Just covering the "bases" more generally.Towe
C
1

If your class that directly extends std::enable_shared_from_this() can be a template class, just do deeper CRTP:

template<class Derived> class A:
public std::enable_shared_from_this<Derived> {};

class B : public A<B> {};

If not and you're on C++23, you can make a more flexible enable_shared_from_this that returns the correct type for any subclasses:

/** Extension of `std::enable_shared_from_this()` with subclassing support.
 *
 * This allows descendants of non-template derived classes to get a correctly
 * typed pointer.
 */
template <class Base>
class enable_shared_from_this : public std::enable_shared_from_this<Base> {
 public:
  template <class Self>
  auto shared_from_this(this Self& self) {
    using decayed_base_t = std::enable_shared_from_this<Base>;
    using base_t = std::conditional_t<
      std::is_const_v<Self>,
      const decayed_base_t,
      decayed_base_t>;

    auto parent = static_cast<base_t&>(self).shared_from_this();
    return static_pointer_cast<std::remove_reference_t<Self>>(parent);
  }

  template <class Self>
  auto weak_from_this(this Self& self) {
    return std::weak_ptr {self.shared_from_this()};
  }
};

If A is not a template class and you're not using C++23, you need to either move to C++23 or make it a template class.

Concentrate answered 3/8, 2024 at 21:15 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.