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

笑着哭i 提交于 2019-12-08 20:01:31

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.

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.

Cheers and hth. - Alf

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.

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