有没有无法锁定(提升为shared_ptr)的weak_ptr?如果没有,为什么?

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

本文关键字:ptr 为什么 weak 如果没有 shared 锁定 有没有      更新时间:2023-10-16

也许这个问题以前被问过,但我从来没有找到一个满意的答案。另外,为了简单起见,假设我说的是一个单线程应用程序。

所以,我多次听到的是,

如果你有一个非所有者的对象,并且它的生存期是有保证的,你应该用原始指针引用它。对象的所有者将使用unique_ptr,并根据需要分发原始指针。

但是,如果对象是非所有者,并且无法保证生存期怎么办?然后你可以使用weak_ptr,是的。但是,任何递给weak_ptr的人都可能很顽皮并将其锁定,这样物体的主人就不会导致物体被破坏。有时这可能不是问题,但有时确实如此。例如,当拥有的对象表示必须在特定时间放弃的某个系统资源时。

你可能会说:"好吧,那么你应该确保没有人锁weak_ptr!但从OO设计的角度来看,这并不理想(在我看来),因为它在"所有者"对象和任何从中获得weak_ptr的对象之间创建了依赖关系。你不妨提出论点"你不需要返回常量引用;你应该确保没有人修改引用。

有了

Qt,你就有了QPointer,这基本上就是我想要的。它检查对象是否尚未销毁,但无法阻止对象被销毁。我意识到这不是线程安全的,但同样,我说的是单个线程的上下文。

那么为什么C++11没有类似的东西呢?我相信我可以围绕weak_ptr做一个包装,以实现我所追求的目标。但我想知道我是否做错了。

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

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函数意识到对象现在获得了它需要的最后一位数据并且它的工作已经完成,它可能会销毁该对象,现在我们正在不再存在的对象上运行一个函数。

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

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

您可以使用

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)。

没有这样的事情,唉。

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

我认为现在我们在语言中有了参数转发支持,这种指针的时机可能已经到来。如果.运算符可能过载,那就更好了。但无论如何,必须非常谨慎地选择功能。


尽管命名,std::weak_ptr并没有真正"锁定"。如果可能的话,它只是用来获得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";
}

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

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

相关文章: