有没有weak_ptr这样的东西不能加锁(提升为shared_ptr)?如果不是,为什么?

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

也许这个问题以前有人问过,但我一直没有找到满意的答案。此外,为了简单起见,假设我在谈论单线程应用程序。

所以,我多次听到的是,如果您有一个 非拥有 并且其 生命周期得到保证的对象,你应该用原始指针引用它。对象的所有者将使用 unique_ptr,并根据需要分发原始指针。

但是如果对象非拥有,并且生命周期是保证?那你可以用一个weak_ptr,是的。但是,任何收到 weak_ptr 的人都可能会调皮并将其锁定,这样对象的所有者就不会导致对象被销毁。有时这可能不是问题,但有时是。例如,当拥有的对象表示某些系统资源时,必须 在特定时间放弃。

你可能会说 "well, then you should just make sure no one keeps the weak_ptr locked!" 但从 OO 设计的角度来看,这并不理想(在我看来),因为它在 "owner" 对象和任何获得 weak_ptr 从中。你不妨提出论点 "you don't need to return const references; you should just make sure no one modifies the reference."

有了 Qt,您就有了 QPointer,这基本上就是我要找的东西。它检查对象是否未被销毁,但它不能防止对象被销毁。我意识到这不是线程安全的,但我再次谈论的是单线程的上下文。

那么为什么 C++11 没有类似的东西呢?我确定我可以围绕 weak_ptr 制作一个包装器来完成我所追求的。但我想知道我是不是做错了。

没有这回事,唉

在 2009 年我 toyed with / explored such a smart pointer type 我称之为 ZPtr,我记得这是对那个方向的一些早期代码的清理工作,在支持比沉默更好的文件抽象错误处理的上下文中处理标准库的 iostream。 (较早的)想法是在没有任何进一步有意义的操作时通过自我毁灭来避免周围有任何僵尸对象,这需要通过可以检测所指对象是否存在的智能指针进行访问。显然这在当时不是一个好主意,因为我发给 DDJ 的文章被拒绝了......被沉默处理。

我认为既然我们在语言中支持参数转发,那么使用这种指针的时机可能已经到来。 . 运算符可能重载会更好。但是无论如何都必须非常谨慎地选择功能。


尽管命名,std::weak_ptr 并不是真正的 "locked"。如果可能,它仅用于获取 std::shared_ptrstd::shared_ptr 随时为您提供原始指针。

所以你可以选择不直接std::weak_ptr分发,而是只提供一个临时原始指针的包装器。

虽然它不会是非常线程安全的,并且与 ZPtr 不同,它不会让客户端代码知道为什么引用对象不再存在(当它不存在时)。但这可能就是您所需要的。让我喝杯咖啡吃点东西,然后我会做一个例子。


示例:

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

哦,为了解决 的问题,对象在某些函数的调用中消失,您可以只提供一个执行函子的成员函数,例如 std::function以原始指针作为参数,其中保证所引用的对象在该调用期间保持活动状态(即通过具有本地 std::shared_ptr).

然后客户端代码程序员可以选择是依赖被调用函数不会破坏对象的假设,还是使用更安全的回调机制。

没有。它不存在是因为即使对于单个线程也是不安全的。考虑:

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

如果 some_function(通过间接路径)导致对象被销毁会怎样?在你说那永远不会发生之前,是的,它会发生。例如:

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

现在,假设 youGotMail 函数意识到对象现在获得了它需要的最后一位数据并且它的工作已经完成,它可能会销毁该对象,现在我们 运行不再存在的对象上的函数。

如果你想要一个原始指针,你知道在哪里可以找到一个。创建一个不比原始指针更安全的 "smart" 指针没有多大意义。

因此,如果您不管理对象的生命周期,则需要先锁定该对象,然后才能对该对象执行任何操作。

您可以使用 shared_ptr<unique_ptr<T>>.

使用纯标准 C++ 执行此操作

观察者只收到一个 shared_ptr<const unique_ptr<T>>,允许他们看但不能触摸。拥有非const 智能指针的所有者可以随时调用unique_ptr 上的reset() 来销毁实例。那个时候所有的观察者也能看到unique_ptr变成了空

明显的线程和重新进入注意事项适用(您需要在每次调用回调后再次检查 unique_ptr 是否具有有效指针等)。

而且如果应该有多个所有者,则需要多做一些工作。您将需要一个 shared_ptr<T*>,给观察员一个 shared_ptr<T* const>。还有一个单独的 shared_ptr<T> 来管理对象的生命周期。 shared_ptr<T*> 需要在对象的析构函数中手动填充 nullptrT*,而不是 shared_ptr)。