Reputation: 5844
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?
Upvotes: 7
Views: 1261
Reputation: 206
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.
Upvotes: 1
Reputation: 72463
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()); }
// ...
};
Upvotes: 2
Reputation: 126478
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()); }
Upvotes: 9