Search code examples
c++shared-ptrmultiple-inheritanceambiguousenable-shared-from-this

Bad_weak_ptr caused by call to shared_from_this with multiple inheritence


I am trying to understand why there is a 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?


Solution

  • 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 generated, 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 enable this functionality.