1

I am trying to understand why there is an bad_weak_ptr exception when calling shared_from_this.

#include <memory>
#include <iostream>

class parent : public std::enable_shared_from_this<parent>
{
public:
    void compare(std::shared_ptr<parent> const& p2)
    {
        std::cout << (this->shared_from_this() == p2->shared_from_this());
    }
};

class child1 : public parent
{};

class child2 : public parent
{};

class child3 : public child1, public child2
{};

void compare(parent& p1, parent& p2)
{
    std::cout << &p1 << " : " << &p2 << "\n";
    std::cout << (&p1 == &p2);
}

void compare(std::shared_ptr<parent> const& p1, std::shared_ptr<parent> const& p2)
{
    compare(*p1, *p2);
//  p1->compare(p2); // bad_weak_ptr
//  auto p = p1->shared_from_this(); // bad_weak_ptr
}

void compareusingchild(std::shared_ptr<child1> const& c1, std::shared_ptr<child2> const& c2)
{
    compare(c1, c2);
}

int main()
{
    std::shared_ptr<child3> c3 = std::make_shared<child3>();
    try
    {
        compareusingchild(c3, c3);
    }
    catch (std::exception& e)
    {
        std::cout << e.what();
    }
    return 0;
}

I found that by making class parent inheritance virtual, this problem doesn't seem to persist. Why isn't this a compile time error? something like 'ambiguous function call' when it could not find the correct inherited parent?

An API containing just the parent class cannot know in advance the inheritance hierarchy and call to compare method (in parent) will cause run-time error. Is it possible to make such errors compile time detectable?

curiousguy
  • 7,344
  • 2
  • 37
  • 52
PVRT
  • 388
  • 2
  • 10
  • 2
    This is a diamond problem. Since your inheritance is not virtual `child3` have two components of `parent` (one from `child1` one from `child2`) and as a result you have two components of `std::enable_shared_from_this`. Please note you are using `std::enable_shared_from_this` in incorrect way. When you called `std::make_shared();` only one version of `std::enable_shared_from_this` has been initialized. – Marek R Aug 29 '19 at 09:33
  • 2
    i do get a compile error https://onlinegdb.com/rkecnzBBB `error: ‘std::enable_shared_from_this’ is an ambiguous base of ‘child3’` with c++14 but it compiles fine with c++17 :( – ustulation Aug 29 '19 at 09:37
  • @ustulation I compiled only with MSVS 2017 untill now. Now that you said it, i tried c++ 11 too and it doesn't compile. So this problem is not relevant for my application as its in c++11. I still would like to know what changed in c++17? – PVRT Aug 29 '19 at 09:49
  • @ustulation surprisingly it compiles: https://godbolt.org/z/Z7MztH – Marek R Aug 29 '19 at 09:49
  • @MarekR I agree its a diamond problem. Shouldn't the compiler detect it? – PVRT Aug 29 '19 at 09:51
  • This is more like compiler difference since with C++11 it also compiles: https://godbolt.org/z/LnsXrK – Marek R Aug 29 '19 at 09:58
  • It also runs without `std::bad_weak_ptr` https://godbolt.org/z/G4t-QE – Marek R Aug 29 '19 at 10:05
  • @MarekR There is no exeption because call to method shared_from_this are commented out. – PVRT Aug 29 '19 at 10:09

1 Answers1

1

Ok Now I see what is the problem.

Diamond problem disables shared_from_this.

Under the hood (for MSVC 2017) you can find something like this:

template<class _Yty,
    class = void>
    struct _Can_enable_shared
        : false_type
    {   // detect unambiguous and accessible inheritance from enable_shared_from_this
    };

template<class _Yty>
    struct _Can_enable_shared<_Yty, void_t<typename _Yty::_Esft_type>>
        : is_convertible<remove_cv_t<_Yty> *, typename _Yty::_Esft_type *>::type
    {   // is_convertible is necessary to verify unambiguous inheritance
    };

So basically when template is generate it checks if conversion from child3 * to std::enable_shared_from_this<parent> * is possible. If it is possible internal weak pointer is set otherwise nothing is done.

Now since there is an ambiguity simple conversion is not possible std::is_convertible returns false and shared_from_this is not enabled (set to proper value).

Here is a proof: https://godbolt.org/z/V2AzLk

        std::cout << "Conv child3: " << std::is_convertible<child3*, std::enable_shared_from_this<parent>*>::value << std::endl;
        std::cout << "Conv child2: " << std::is_convertible<child2*, std::enable_shared_from_this<parent>*>::value << std::endl;
        std::cout << "Conv child1: " << std::is_convertible<child1*, std::enable_shared_from_this<parent>*>::value << std::endl;

prints:

Conv child3: 0
Conv child2: 1
Conv child1: 1

So basically ambiguity doesn't cause compilation issue it just doesn't enables this functionality.

Marek R
  • 23,155
  • 5
  • 37
  • 107
  • This is what I hate that implicit init of the `weak_ptr` member: if the "magic" doesn't work it won't ever tell you, as deriving from `enable_shared_from_this` is optional. There are many Q on SO about failing `shared_from_this`, notably from private inheritance. Also it won't work if you have a smart ptr to a member and not a base. – curiousguy Nov 06 '19 at 08:57