shared_ptr<>是weak_ptr<>,就像unique_ptr<>是...什么?

shared_ptr<> is to weak_ptr<> as unique_ptr<> is to... what?

本文关键字:lt gt ptr 什么 unique weak shared 就像      更新时间:2023-10-16

在 C++11 中,您可以使用shared_ptr<>与对象或变量建立所有权关系,并weak_ptr<>以非所有者的方式安全地引用该对象。

还可以使用 unique_ptr<> 与对象或变量建立所有权关系。但是,如果其他非拥有对象也想要引用该对象怎么办? 在这种情况下,weak_ptr<>没有帮助。原始指针很有帮助,但带来了各种缺点(例如,它们可以自动初始化为 nullptr,但这是通过与std::*_ptr<>类型不一致的技术来实现的(。

对于通过unique_ptr<>拥有的对象的非所有权引用,相当于weak_ptr<>什么?

这里有一个澄清性的例子,类似于我正在开发的游戏中的东西。

class World
{
public:
    Trebuchet* trebuchet() const { return m_trebuchet.get(); }
private:
    std::unique_ptr< Trebuchet > m_trebuchet;
};
class Victim
{
public:
    Victim( Trebuchet* theTrebuchet ) : m_trebuchet( theTrebuchet ) {}
    ~Victim()
    {
        delete m_trebuchet;     // Duh. Oops. Dumb error. Nice if the compiler helped prevent this.
    }
private:
    Trebuchet* m_trebuchet;    // Non-owning.
};
shared_ptr< Victim > createVictim( World& world )
{
    return make_shared< Victim >( world.trebuchet() );
}

在这里,我们使用原始指针来维护与通过其他地方unique_ptr<>拥有的对象的非所有者关系。但是,原始是我们能做的最好的事情吗?

希望是一种指针类型,它:

  • 看起来像其他现代指针类型。 例如 std::raw_ptr<T> .
  • 替换原始指针,以便始终使用新式指针类型的代码库可以通过搜索_ptr<(大致(找到所有指针。
  • 自动初始化为空点。

因此:

int* p;                  // Unknown value.
std::raw_ptr< int > p;   // null.

这种类型现在是否已经存在于C++中,是针对未来提出的,还是在例如 Boost 中广泛使用的另一种实现?

shared_ptr的"通知"行为需要引用计数控制块的引用计数。 shared_ptr 的引用计数控制块为此使用单独的引用计数。 weak_ptr实例维护对此块的引用,并且weak_ptr本身阻止引用计数控制块被delete编辑。指向对象在强计数变为零时调用其析构函数(这可能会导致也可能不会导致存储该对象的内存delete离子(,并且仅当弱引用计数变为零时,才会delete控制块。

unique_ptr 的宗旨是它在普通指针上的开销为零。分配和维护引用计数控制块(以支持weak_ptr语义(会破坏这一原则。如果您需要该描述的行为,那么您确实需要共享语义,即使对该对象的其他引用是非拥有的。在这种情况下,仍然有共享 - 共享对象是否已被破坏的状态。

如果需要通用非所有者引用并且不需要通知,请使用纯指针或对unique_ptr中的项的纯引用。


编辑:

在您的示例中,看起来Victim应该要求Trebuchet&而不是Trebuchet*。然后很清楚谁拥有有问题的对象。

class World
{
public:
    Trebuchet& trebuchet() const { return *m_trebuchet.get(); }
private:
    std::unique_ptr< Trebuchet > m_trebuchet;
};
class Victim
{
public:
    Victim( Trebuchet& theTrebuchet ) : m_trebuchet( theTrebuchet ) {}
    ~Victim()
    {
        delete m_trebuchet;     // Compiler error. :)
    }
private:
    Trebuchet& m_trebuchet;    // Non-owning.
};
shared_ptr< Victim > createVictim( World& world )
{
    return make_shared< Victim >( world.trebuchet() );
}
确实

需要标准指针类型来充当std::unique_ptr<>的非拥有、廉价且行为良好的对位。目前还没有这样的指针被标准化,但已经提出了一个标准,C++标准委员会正在讨论中。"世界上最愚蠢的智能指针",又名std::exempt_ptr<>将具有其他现代C++指针类的一般语义,但不负责拥有指向的对象(如shared_ptrunique_ptr所做的那样(或正确响应该对象的删除(如weak_ptr所做的那样(。

假设这一特征最终得到委员会的批准,它将完全满足本问题中强调的需要。即使没有得到委员会的批准,上述链接文件也充分表达了需求并描述了一个完整的解决方案。

unique_ptr的非欠模拟是一个普通的C指针。有什么不同 - C 指针不知道指向的数据是否仍然可以访问。 另一方面,weak_ptr确实如此。但是,不可能在没有额外开销的情况下将指针替换为知道数据有效性raw指针(weak_ptr确实有这种开销(。这意味着 C 型指针在速度方面是最好的,您可以作为 unique_ptr 的非欠模拟。

虽然你不能免费获得指向唯一拥有对象的"弱"指针,但这个概念很有用,并且在几个系统中使用。参见 Chromium 的 WeakPtr 和 QT 的 QPointer 了解实现。

Chromium的WeakPtr是通过在弱引用对象中存储shared_ptr并在对象被销毁时将其标记为无效来实现的。然后,WeakPtrs 引用该 ControlBlock 并检查它是否有效,然后再分发他们的原始指针。我假设QT的QPointer是类似实现的。由于所有权不共享,因此原始对象将被确定性地销毁。

但是 ,这意味着取消引用WeakUniquePtr不是线程安全的:

线程 1:

unique_ptr<MyObject> obj(new MyObject);
thread2.send(obj->AsWeakPtr());
...
obj.reset();  // A

线程 2:

void receive(WeakUniquePtr<MyObject> weak_obj) {
  if (MyObject* obj = weak_obj.get()) {
    // B
    obj->use();
  }
}

如果第 A 行碰巧与第 B 行同时运行,则线程 2 将使用悬空指针结束。 std::weak_ptr可以通过在让线程 2 使用它之前以原子方式获取对对象的共享拥有引用来防止此问题,但这违反了上述假设,即对象是唯一拥有的。这意味着WeakUniquePtr的任何使用都需要与真实对象的销毁同步,最简单的方法是要求它们在同一线程上的消息循环中完成。(请注意,在使用之前,跨线程来回复制WeakUniquePtr仍然是完全安全的。

可以想象在std::unique_ptr中使用自定义删除器来使用标准库类型来实现这一点,但这留给读者作为练习。

boost::optional<Trebuchet&>

正如Billy ONeal在他的回答中指出的那样,你可能想要传递一个Trebuchet&而不是一个指针。引用的问题在于你不能传递nullptrboost::optional提供了一种具有nullptr等价的方法。有关 boost::可选 的更多详细信息,请访问:http://www.boost.org/doc/libs/1_54_0/libs/optional/doc/html/boost_optional/detailed_semantics.html

另请参阅此问题:boost::optional<T&amp;> vs T*

注意:std::optional<T>有望进入C++14,但std::optional<T&>是一个单独的提案,不在当前的C++14草案中。更多细节在这里:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3672.html<</p>

div class="answers">

在具有shared_ptr、weak_ptr和unique_ptr的新C++世界中,您不应该使用原始指针或引用存储对对象(如投石机(的长寿命引用。相反,世界应该对投石机有一个shared_ptr,受害者应该存储一个shared_ptr或一个weak_ptr,这取决于如果世界消失,投石机是否应该留在受害者身边。使用weak_ptr可以判断指针是否仍然有效(即世界仍然存在(,无法使用原始指针或引用来执行此操作。

当您使用unique_ptr时,您声明只有 World 实例将拥有投石机。World 类的客户端可以通过调用 "get" 方法使用 World 对象的投石机,但在使用完该方法后不应保留该方法返回的引用或指针。相反,他们应该在每次想要使用投石机时通过调用"get"方法来"借用"投石机。

综上所述,在某些情况下,您可能希望存储引用或原始指针以供将来使用,以避免shared_ptr开销。但是这些实例很少而且相距甚远,您需要完全确定在拥有投石机的 World 对象消失后不会使用指针或引用。

otn::raw::weak(来自C++对象令牌库(是非拥有的,便宜的,并且行为良好的std::unique_ptr对立。同样在库中有 otn::safe::unique ,一个唯一的所有者,它可以"通知"非拥有otn::safe::weak有关删除对象的信息。

#include <otn/all.hpp>
#include <iostream>
int main()
{
    using namespace std;
    using namespace otn;
    raw::weak_optional<int> raw_weak;
    if (!raw_weak)
        cout << "raw_weak is empty" << endl;
    cout << "--- create object in std_unique..." << endl;
    auto std_unique = std::make_unique<int>(42);
    raw_weak = std_unique;
    if (std_unique)
        cout << "std_unique is not empty" << endl;
    if (raw_weak)
        cout << "raw_weak is not empty" << endl;
    cout << "--- move std_unique to safe_unique..." << endl;
    safe::unique_optional<int> safe_unique = std::move(std_unique);
    if (!std_unique)
        cout << "std_unique is empty" << endl;
    if (raw_weak)
        cout << "raw_weak is not empty, it is observs safe_unique" << endl;
    safe::weak_optional<int> safe_weak = safe_unique;
    if (safe_unique)
        cout << "safe_unique is not empty" << endl;
    if (!safe_weak.expired())
        cout << "safe_weak is not expired" << endl;
    cout << "--- destroy object in safe_unique..." << endl;
    utilize(std::move(safe_unique));
    if (!safe_unique)
        cout << "safe_unique is empty" << endl;
    if (safe_weak.expired())
        cout << "safe_weak is expired, it is not dangling" << endl;
    if (raw_weak)
        cout << "raw_weak is not empty, it is dangling!!!" << endl;
}

输出:

raw_weak is empty
--- create object in std_unique...
std_unique is not empty
raw_weak is not empty
--- move std_unique to safe_unique...
std_unique is empty
raw_weak is not empty, it is observs safe_unique
safe_unique is not empty
safe_weak is not expired
--- destroy object in safe_unique...
safe_unique is empty
safe_weak is expired, it is not dangling
raw_weak is not empty, it is dangling!!!

采用原始指针或引用的函数隐式承诺在函数返回后不会保留该指针的副本。作为回报,调用方承诺指针有效(或nullptr(,直到被调用方返回。

如果要保留指针,则正在共享它(并且应使用shared_ptr(。unique_ptr管理指针的单个副本。使用原始指针(或引用(来引用涉及该对象的调用函数。

对于shared_ptr对象也是如此。 只有当您想要对比所涉及的函数更长久的指向 too 对象进行额外引用时,weak_ptr才会发挥作用。weak_ptr的主要目的是打破两个对象相互保存引用的引用循环(因此永远不会释放(。

但请记住,采用shared_ptrweak_ptr意味着采用该参数的函数将(可选(修改其他一些对象,以保留对指向对象的引用,该对象的寿命超过函数的调用。在绝大多数情况下,即使对于shared_ptr或weak_ptr,也可以使用原始指针(如果nullptr是有效值(或 ref(当值得到保证时(。

现在你noshared_ptr --

https://github.com/xhawk18/noshared_ptr

noshared_ptr<T> -- a new type of unique ptr
noweak_ptr<T>   -- weak ptr for the noshared_ptr