Is there such thing as a weak_ptr that can't be locked (promoted to shared_ptr)? If not, why?

心已入冬 提交于 2019-12-10 11:46:29

问题


Maybe this question has been asked before, but I've never found a satisfactory answer. Also, for the purposes of simplicity assume I'm talking about a single-threaded application.

So, what I've heard a number of times is that if you have an object that is non-owned and whose lifetime is guaranteed, you should reference it with a raw pointer. The object's owner would use a unique_ptr, and hand out raw pointers as necessary.

But what if the object is non-owned, and the lifetime is not guaranteed? Then you can use a weak_ptr, yes. But then anyone who is handed a weak_ptr could be naughty and keep it locked, such that the object's owner can't cause the object to be destroyed. Sometimes this may not be a problem, but sometimes it is. For example, when the owned object represents some system resource which must be relinquished at a certain time.

You may say "well, then you should just make sure no one keeps the weak_ptr locked!" But that is just not ideal (in my opinion) from an OO design standpoint, as it creates a dependency between the "owner" object and any object that gets a weak_ptr from it. You might as well make the argument "you don't need to return const references; you should just make sure no one modifies the reference."

With Qt, you have the QPointer, which is basically what I'm looking for. It checks that the object hasn't been destroyed, but it can't prevent the object from being destroyed. I realize this isn't thread-safe, but again, I'm talking about the context of a single thread.

So why isn't there something similar for C++11? I'm sure I could make a wrapper around weak_ptr that accomplishes what I'm after. But I wonder if I'm going about this all wrong.


回答1:


You can do this with pure Standard C++ using shared_ptr<unique_ptr<T>>.

Observers received only a shared_ptr<const unique_ptr<T>>, allowing them to look but not touch. The owner, having a non-const smart pointer, can at any time call reset() on the unique_ptr to destroy the instance. At that time all the observers can also see that the unique_ptr has become empty.

Obvious threading and re-entrance caveats apply (you need to check the unique_ptr for having a valid pointer again after each callback you invoke, etc).

And if there should be multiple owners, it's a bit more work. You will need one shared_ptr<T*>, giving observers a shared_ptr<T* const>. And a separate shared_ptr<T> to manage the object lifetime. The shared_ptr<T*> will need to be manually filled with nullptr (The T*, not the shared_ptr) in the object's destructor.




回答2:


No. And it doesn't exist is because it's unsafe even for a single thread. Consider:

void some_function (super_weak_ptr foo)
{
    foo->some_function();
}

What happens if some_function (through an indirect path) causes the object to be destroyed? And before you say that can never happen, yes it can. For example:

void got_some_data (some_type whatObject, some_other_type whatData)
{
    super_weak_ptr object = findObject (whatObject);
    if (object)
        object->youGotMail (whatData);        
}

Now, suppose the youGotMail function realizes that the object now got the last bit of data it needs and its job is complete, it might destroy that object, and now we're running a function on an object that no longer exists.

If you want a raw pointer, you know where to find one. It doesn't make much sense to create a "smart" pointer that's no safer than a raw pointer.

So if you're not managing the lifetime of an object, you need the ability to lock that object before you can do anything to that object.




回答3:


There is no such thing, alas.

In 2009 I toyed with / explored such a smart pointer type that I called ZPtr, as I recall as a cleanup effort of some earlier code in that direction, in the context of supporting better file abstraction error handling than the silence treatment of the standard library's iostreams. The (earlier) idea was to not have any zombie objects around, by self-destroying when no further meaningful operations were possible, and this required access via a smart pointer that could detect the existence or not of the referent. Evidently it was not such a good idea at the time, because the article I sent to DDJ about it was rejected … by the silence treatment.

I think now that we have argument forwarding support in the language, the time for such a pointer may have come. It would be even better with possible overloading of the . operator. But the functionality will anyway have to be chosen very carefully.


The std::weak_ptr isn't really "locked", in spite of naming. It's just used to obtain a std::shared_ptr, if possible. And a std::shared_ptr readily supplies you with a raw pointer.

So you can choose to hand out not std::weak_ptr directly, but a wrapper that only provides a temporary raw pointer.

It will not be very thread safe though, and unlike a ZPtr it will not give the client code any idea of why the referent doesn't exist any more (when it doesn't). But it just may be all that you need. Let me get some coffee & a bite to eat, then I'll cook up an example.


Example:

#include <memory>

namespace cppx {
    using std::shared_ptr;
    using std::weak_ptr;

    template< class Type >
    class Poor_ptr
    {
    private:
        struct Null {};
        weak_ptr<Type>      weak_p_;

    public:
        explicit operator bool() const { return not is_null(); }

        friend
        auto operator==( const Poor_ptr& p, Poor_ptr::Null* )
            -> bool
        { return p.is_null(); }

        friend
        auto operator==( Poor_ptr::Null*, const Poor_ptr& p )
            -> bool
        { return p.is_null(); }

        friend
        auto operator!=( const Poor_ptr& p, Poor_ptr::Null* )
            -> bool
        { return not p.is_null(); }

        friend
        auto operator!=( Poor_ptr::Null*, const Poor_ptr& p )
            -> bool
        { return not p.is_null(); }

        auto is_null() const
            -> bool
        { return (ptr_or_null() == nullptr); }

        auto ptr_or_null() const
            -> Type*
        {
            try
            {
                return weak_p_.lock().get();
            }
            catch( ... )
            {
                return nullptr;
            }
        }

        auto ptr() const
            -> Type*
        { return weak_p_.lock().get(); }

        Poor_ptr( shared_ptr< Type > p )
            : weak_p_( p )
        {}
    };

}  // namespace cppx

#include <iostream>
using namespace std;
auto main() -> int
{
    cout << boolalpha;
    auto p = make_shared<int>( 42 );
    cppx::Poor_ptr<int> pp = p;
    cout
        << "That " << pp.ptr_or_null() << " is null is "
        << (pp == 0) << ", not " << !!pp << ".\n";
    p.reset();
    cout
        << "That " << pp.ptr_or_null() << " is null is "
        << (pp == 0) << ", not " << !!pp << ".\n";
}

Oh, and to cover the problem that David Schwartz mentions, that of the object disappearing in mid-call of some function, you can just provide a member function that executes a functor such as std::function with a raw pointer as argument, where the referred to object is guaranteed kept alive during that call (namely by having a local std::shared_ptr).

Then the client code programmer can choose whether to rely on an assumption that called functions will not destroy the object, or use the safer callback mechanism.



来源:https://stackoverflow.com/questions/28954015/is-there-such-thing-as-a-weak-ptr-that-cant-be-locked-promoted-to-shared-ptr

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!