shared_from_this has empty _M_weak_this even after ownership by std::shared_ptr
Asked Answered
S

2

9

I'm storing a class (let's call it A) in an std::vector using C++ smart pointers (so the vector sigature is std::vector<std::shared_ptr<A>>).

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

class A : std::enable_shared_from_this<A> {
public:
  void doWork();
  std::shared_ptr<A> getSharedRef();
};

void A::doWork() { std::cout << "Working..." << std::endl; }

std::shared_ptr<A> A::getSharedRef() { return shared_from_this(); }

class AManager {
  static std::vector<std::shared_ptr<A>> aList;

public:
  static void init(); // safety because statics
  static void doLotsOfWork();
  static std::shared_ptr<A> createA();
};

std::vector<std::shared_ptr<A>> AManager::aList;

void AManager::init() { aList = std::vector<std::shared_ptr<A>>{}; }

void AManager::doLotsOfWork() {
  for (auto a : aList) {
    a->doWork();
  }
}

std::shared_ptr<A> AManager::createA() {
  std::shared_ptr<A> a = std::make_shared<A>();

  aList.push_back(a);
  return a->getSharedRef(); // <----- EXCEPTION
}

int main() {
  AManager::init();
  AManager::createA();
  return 0;
}

For some reason, this throws a std::bad_weak_ptr, and upon inspection I notice that a, for whatever reason, has an _M_weak_this equal to 0x0, or NULL. Since I've already created a valid std::shared_ptr referencing the object, it shouldn't be empty.

Furthermore, I know no memory corruption is occurring because A (with variables) is completely intact at its address.

What am I doing wrong?

Stand answered 12/12, 2017 at 12:2 Comment(16)
Your description is pretty good. It would be even better to provide code that reproduces the error exactly. Look into how to create a minimal reproducible exampleStirring
The biggest problem with an MCVE in this particular case is the code relies on a lot of moving parts, and I'm not exactly sure what parts are producing the error. I do have a github link for the code in question, but otherwise it would take a few hours to spin up a working example.Stand
why do you need getsharedref() ? (and not just use shared_from_this() directly?) is it because A inherits privately ? if yes, that could be the problem ...Totten
@CalmBit, I understand where you're coming from; it's not always easy to create a MCVE, but it is often a necessary step. In addition to making your question very answerable, you may find that you solve the problem yourself along the way (and if not, you'll always learn something). You may also find that you were asking the wrong question (You thought the problem was due to X, but it was really due to Y). Finally, and this may sound ridiculous, but creating an MCVE indicates that you respect the answerer's time and do not expect them to be psychic.Stirring
@Stirring - I'm definitely not expecting anyone here to be psychic! And I do respect your time, albeit I'm slightly burnt from having wasted about 6 hours of mine on this single bug - apologies if I'm coming off a little rough. MCVE will be added as soon as I can find the breakage point.Stand
What kind of inheritance did you use ? Are you sure, A class inherits enable_shared_from_this with public ?Multiversity
@CalmBit You're not coming off as rough at all! Very pleasant, actually. I may have gotten carried away trying to mentor you in the ways of the fickle C++ community; it looks like you've been around long enough to know as much.Stirring
Do you mean to have 2 copies of the shared_ptr<A>, one being a and another one in the vector and then ask for a third one with a->getSharedRef() ?Guilder
@Multiversity - yes, A inherits publicly.Stand
@GuillaumeGranié - I'm looking to create the object, store a shared_ptr<A> in the vector, and then ask for (and return) another shared_ptr<A> referencing the same object. Perhaps I'm misusing smart pointers - they're a little touchy, and I'm a little unsure if I'm going about this in the "correct" way.Stand
When you push_back(a) it effectively creates a copy of it. Afterwards, you have a and the shared_ptr<A> in the vector that points to the class instance. You could create A in-place v.emplace_back(std::make_shared<A>()); (if your shared_ptr vector is v) so that only v has a pointer to it.Guilder
What version,of vs are you using? very old versions had a bug round here iircCoolant
@Stirring Alright, MVCE posted. And for the record - not VS, just plain ol' gcc and a text editor with formatting.Stand
The MCVE needs to be in the question, not on a third-party site (particularly not one that hides its content - is it Javascript-dependent or something?). Please include your code in the body of the question as How to Ask requires. Thanks.Eohippus
indeed, you're inheriting privately from enable_shared_from_this... or is it a typo of the mcve ?Totten
@MassimilianoJanes Wow. I am not only as blind as a bat but apparently incapable of double-checking. That's almost ten hours down the drain - I'm really sorry for bothering anyone with this garbage.Stand
S
14

The problem appears to be because you are inheriting privately from enable_shared_from_this

shared_from_this requires "enable_shared_from_this<T> shall be an accessible base class of T." (according to [util.smartptr.enab])

By inheriting privately, the base class is not accessible, and so the preconditions are violated. I presume this means undefined behavior. Both Clang and GCC throw an exception.

The solution is to inherit publicly.

class A :  public std::enable_shared_from_this<A> {
    //...
};

*In C++17 the wording appears to have moved to [util.smartptr.shared.const], but the requirement is basically the same.

Stirring answered 12/12, 2017 at 16:26 Comment(3)
It's unfortunate that this can't be detected at compile time. I believe the problem is that std::make_shared() couldn't detect that the class derived from enable_shared_from_this, because it was private, so the ref counts are in a separate memory block instead of using the members of enable_shared_from_this, which is why shared_from_this() believes it's wrong and throws. Since enable_shared_from_this is a CRTP, can't the STL library define a dummy public verification method and try calling itself via the CRTP class, causing compilation to fail due to privacy?Keystone
minor correction: the ref counts are in the same spot as usual since it's using make_shared, but the internal member weak_ptr in enabled_shared_from_this isn't getting set because it was private.Keystone
"It's unfortunate that this can't be detected at compile time" Then don't rely on this very strange hacked up tool. Have a weak_ptr that you initialize yourself. Nothing hidden, magical or just implicit.Shauna
D
1

EDIT: I have found a counter-example in my code which invalidates what I originally wrote below. There is another subtlety which can lead to std::enable_shared_from_this not being initialized properly which I haven't successfully understood yet.

Even though ~andyg's answer is correct, it isn't always sufficient to make the base class public. I ran into a difficult situation with a nullptr weak_ptr like this and I had to find out the hard way that the class with the public std::enable_shared_from_this base class can't be sitting inside an anonymous namespace. Hoping this information can help someone in the future!

Dermatophyte answered 20/9, 2023 at 0:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.