的shared_ptr<>是对weak_ptr<> as unique_ptr<>是......什么?

时间:2013-07-08 22:01:26

标签: c++ c++11 unique-ptr

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

您还可以使用unique_ptr<>与对象或变量建立所有权关系。但是,如果其他非拥有对象想要引用该对象呢? weak_ptr<>在这种情况下无用。原始指针很有用但会带来各种缺点(例如,它们可以是automatically initialized to nullptr,但这是通过与std::*_ptr<>类型不一致的技术实现的。

对于通过weak_ptr<>拥有的对象的非拥有引用,unique_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<(粗略地)找到所有指针。
  • 自动初始化为nullptr。

因此:

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

这种类型现在是否已经存在于C ++中,是否为将来提出,或者是另一种广泛可用的实现方式,例如:升压?

8 个答案:

答案 0 :(得分:38)

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

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

如果您需要通用的非常规引用且不需要通知,请使用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() );
}

答案 1 :(得分:21)

真正需要一种标准指针类型作为std::unique_ptr<>的非拥有,廉价且表现良好的对应点。没有这样的指针已经标准化,但是standard has been proposed并且正在由C ++标准委员会讨论。 “世界上最愚蠢的智能指针”,又名std::exempt_ptr<>将具有其他现代C ++指针类的一般语义,但对拥有指向对象(shared_ptr和{{1})不承担任何责任。 })或正确响应删除该对象(如unique_ptr那样)。

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

答案 2 :(得分:7)

unique_ptr的非欠模拟是一个普通的C指针。有什么不同 - C指针不知道指向的数据是否仍然可访问。另一方面weak_ptr。但是不可能用指针替换raw指针,而不需要额外的开销就知道数据的有效性(weak_ptr确实有这种开销)。这意味着C风格的指针在速度方面是最好的,你可以作为unique_ptr的非欠模拟。

答案 3 :(得分:6)

虽然你无法免费获得一个独特拥有对象的“弱”指针,但这个概念很有用,并且在一些系统中使用。有关实施,请参阅Chromium's WeakPtrQT's 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可以通过atomically taking a shared owning reference to the object before letting thread 2 use it阻止此问题,但这违反了上述假设,即对象是唯一拥有的。这意味着任何使用WeakUniquePtr都需要与真实对象的销毁同步,最简单的方法是要求它们在同一个线程的消息循环中完成。 (请注意,在使用它之前,在线程之间来回复制WeakUniquePtr仍然是完全安全的。)

可以想象在std::unique_ptr中使用自定义删除器来使用标准库类型来实现它,但这仍然是读者的练习。

答案 4 :(得分:3)

boost::optional<Trebuchet&>

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

另请参阅此问题:boost::optional<T&> 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

答案 5 :(得分:1)

在使用shared_ptr,weak_ptr和unique_ptr的新C ++世界中,您不应该使用原始指针或引用来存储对象(如投石机)的长期引用。相反,World应该有一个shared_ptr到trebuchet,受害者应该存储shared_ptr或weak_ptr,这取决于如果世界消失,投石机是否应该与受害者保持联系。使用weak_ptr可以判断指针是否仍然有效(即世界仍然存在),没有办法用原始指针或引用来做到这一点。

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

上面说过,可能存在一些实例,您希望存储引用或原始指针以供将来使用,以避免shared_ptr的开销。但是那些实例很少而且很远,你需要完全确定在拥有投石机的World对象消失后你不会使用指针或引用。

答案 6 :(得分:1)

otn::raw::weak(来自C++ Object Token Library)是对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!!!

答案 7 :(得分:0)

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

如果你想要抓住指针,你就是在分享它(并且应该使用shared_ptr)。 unique_ptr管理指针的单个副本。您可以使用原始指针(或引用)来引用涉及该对象的调用函数。

shared_ptr个对象的情况相同。 weak_ptr只有在您希望对超出所涉及功能的指向太对象的附加引用时才会发挥作用。 weak_ptr的主要目的是打破两个对象彼此保持引用的引用周期(因此永远不会被释放)。

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