std::enable_shared_from_this; public vs private
Asked Answered
I

4

26

I was playing around for a bit using the shared_ptr's and enable_shared_from_this, while I run into something I don't really understand.

In my first attempt I constructed something like this:

class shared_test : std::enable_shared_from_this<shared_test> {
public:
    void print(bool recursive) {
        if (recursive) {
            shared_from_this()->print(false);
        }

        std::cout << "printing" << std::endl;
    }
};

Please note that this class is extending std::enable_shared_from_this privately. This apparently makes a lot of difference because executing a something like this:

int main() {
    auto t(std::make_shared<shared_test>());
    t->print(true);
    return 0;
}

throws an bad_weak_ptr exception. Where as if I change the class definition to inherent publicly from std::enable_shared_from_this this runs just find.

Why is that, what do I miss here? And isn't there a way to make it work for private inheritance, since the 'outside world' of the shared_test class does not need to know that it is enabling shared from this... (at least, not if you ask me, or do I miss something again?)

Illuminism answered 8/10, 2016 at 20:34 Comment(0)
A
19

Why is that, what do I miss here?

To make shared_from_this work enable_shared_from_this has to know about shared_ptr which holds the class. In your STL implementation it is weak_ptr, through other implementations are possible. When you inherit privately, then it is not possible to access base class's properties from the outside of your class. Actually it is not even possible to understand that you have inherited from. So make_shared generates usual shared_ptr initialization without setting proper fields in enable_shared_from_this.

Exception is thrown not from make_shared but form shared_from_this because enable_shared_from_this was not initialized properly.

And isn't there a way to make it work for private inheritance, since the 'outside world' of the shared_test class does not need to know that it is enabling shared from this...

No. Outside world has to know that the object has special relations with shared_ptr to work properly with it.

Allpurpose answered 8/10, 2016 at 20:40 Comment(7)
It's interesting still that the error is a runtime error rather than a private access compilation error. In all other cases I can think of, there is absolutely no difference in layout, resolition, ... . Access control is done last and independently, to the point where #define private public gives the same object file.Auxin
@JohanLundberg For the outside world, there is no (logical) difference between private inheritance and no inheritance. Private inheritance means is_base_of<enable_shared_from_this<shared_test>, shared_test> is false, which is a compile-time test which controls what code is generated by the shared_ptr constructor.Schaaff
@Oktalist. Thank you - I follow. What I wrote in my comment clearly does not hold for inheritance.Auxin
@JohanLundberg exception is thrown not from make_shared but form shared_from_this. The reason is simple: because of private inheritance make_shared did not see that your class is based on enable_shared_from_this. So no code was generated for initialization of weak_ptr located in enable_shared_from_this. It just holds it's default uninitialized value. When you call shared_from_this it looks in to that weak_ptr and throws the exception.Allpurpose
@Schaaff "there is no (logical) difference between private inheritance and no inheritance" That's incorrect: private inheritance of D from B allows a viable conversion from D* to B*, so it makes a huge difference.Refulgent
@Refulgent Only if D is a standard-layout class with no non-static data members?Schaaff
@Schaaff No, I mean that at compile time the conversion is viable so a call of a function taking a B* (or B&) parameter is viable. So private inheritance affects overloading, even if the inheritance is not accessible and will cause an error if you access the private base.Refulgent
S
7

isn't there a way to make it work for private inheritance, since the 'outside world' of the shared_test class does not need to know that it is enabling shared from this

shared_ptr itself is part of the 'outside world'; the shared_ptr constructor needs to be able to access the enable_shared_from_this base class subobject of the shared_test object it points to, in order to initialize the private weak_ptr member of the enable_shared_from_this implementation.

Schaaff answered 8/10, 2016 at 20:45 Comment(0)
D
2

I analyse this question from code in STL:

auto t(std::make_shared());

the code line construct a shared_ptr;First we dive into the make_shared function

 // FUNCTION TEMPLATE make_shared
   template<class _Ty,
   class... _Types>
   NODISCARD inline shared_ptr<_Ty> make_shared(_Types&&... _Args)
   {    // make a shared_ptr
     const auto _Rx = new _Ref_count_obj<_Ty>(_STD forward<_Types>(_Args)...);

     shared_ptr<_Ty> _Ret;
    _Ret._Set_ptr_rep_and_enable_shared(_Rx->_Getptr(), _Rx);
     return (_Ret);
   }

caution: we dive into function _Ret.Set_ptr_rep_and_enable_shared.And we can see follows:

template<class _Ux>
   void _Set_ptr_rep_and_enable_shared(_Ux * _Px, _Ref_count_base * _Rx)
   {    // take ownership of _Px
        this->_Set_ptr_rep(_Px, _Rx);
        _Enable_shared_from_this(*this, _Px);
   }

So we find function _Enable_shared_from_this ,the go on:

 template<class _Other,
    class _Yty>
    void _Enable_shared_from_this(const shared_ptr<_Other>& _This, _Yty * _Ptr)
    {   // possibly enable shared_from_this
    _Enable_shared_from_this1(_This, _Ptr, _Conjunction_t<
        negation<is_array<_Other>>,
        negation<is_volatile<_Yty>>,
        _Can_enable_shared<_Yty>>{});
   }

We find a key point: _Can_enable_shared<_Yty>

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
    };

we find only _Yty has _Esft_type and _Yty can be converted to _Esft_type, can _Yty can be enable_shared(If you want to know more, that's to see set weak_ptr in _Yty, or you may get bad_weak_ptr error when you use shared_from_this). So what _Esft_type is?

 template<class _Ty>
    class enable_shared_from_this
    {   // provide member functions that create shared_ptr to this
public:
    using _Esft_type = enable_shared_from_this;
     ...
   }

so _Esft_type just mean enable_shared_from_this<_Ty>, so if you use private inherit, outside not only cann't see _Esft_type and _Yt cann't be converted to _Esft_type. So weak_ptr cann't be set so bad_weak_ptr may be invoked.

So outside need know the existence of _Esft_type, so when shared_ptr is constructed, weak_ptr of shared_test can also be set.

Directly answered 30/5, 2018 at 1:14 Comment(0)
K
0

Based on documentation, it is mandatory to inherit publicly for the accessiblity of "shared_from_this" member function.

"Publicly inheriting from std::enable_shared_from_this provides the type T with a member function shared_from_this" - from CPP reference http://en.cppreference.com/w/cpp/memory/enable_shared_from_this

shared_from_this:

returns a shared_ptr which shares ownership of *this (public member function)

Karinakarine answered 8/10, 2016 at 20:42 Comment(4)
Your answer does not actually add anything new to the question: shared_from_this has to be inherited publicly. The interesting part is why there is such limitation.Allpurpose
"enable_shared_from_this" should be inherited..... not "shared_from_this". And "shared_from_this" is a function inside the base class which returns "*this" pointer. Please make a note that, there is an additional word "enable_" between the class name and its function name.Karinakarine
And you know right....Protected & public members of Base class become private members of Derived class. So, no members of Base class can be accessed by other classes through Derived class object as they are private in Derived class.Karinakarine
read carefully the word "for the accessiblity of" in my answer. It will explain my answer.Karinakarine

© 2022 - 2024 — McMap. All rights reserved.