57

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

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

weak_ptr<>对 via 拥有的对象的非拥有引用等价于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++ 中,是为未来提出的,还是在例如 Boost 中广泛可用的另一种实现?

4

8 回答 8

38

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

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

如果您需要通用的非拥有引用并且不需要通知,请使用纯指针或纯引用unique_ptr.


编辑:

在您的示例中,看起来Victim应该要求 aTrebuchet&而不是 a 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() );
}
于 2013-07-08T22:09:21.937 回答
23

确实需要一种标准指针类型来充当std::unique_ptr<>. 目前还没有这样的指针被标准化,但是已经提出了一个标准,并且正在由 C++ 标准委员会讨论。“世界上最愚蠢的智能指针”,又名std::exempt_ptr<>将具有其他现代 C++ 指针类的一般语义,但对于拥有指向的对象(如shared_ptrunique_ptr做)或正确响应该对象的删除(如weak_ptr做)。

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

于 2013-07-10T02:16:16.790 回答
8

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

于 2013-07-08T22:25:53.417 回答
7

虽然您不能免费获得指向唯一拥有的对象的“弱”指针,但这个概念很有用并且在几个系统中使用。有关实现,请参阅Chromium 的 WeakPtrQT 的 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();
  }
}

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

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

于 2013-07-14T17:29:05.567 回答
3
boost::optional<Trebuchet&>

正如比利奥尼尔在他的回答中指出的那样,您可能想要传递 aTrebuchet&而不是指针。参考的问题是你不能通过 a nullptrboost::optional提供了一种方法来获得 a 的等价物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

于 2013-07-12T00:12:00.083 回答
1

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

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

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

于 2013-07-10T00:03:21.260 回答
1

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!!!
于 2020-03-21T12:27:38.750 回答
0

采用原始指针或引用的函数隐含地承诺在函数返回后不会保留该指针的副本。作为回报,调用者承诺nullptr在被调用者返回之前指针是有效的(或)。

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

对象也是shared_ptr如此。weak_ptr仅当您想要对指向的对象进行额外引用时才起作用,该对象的寿命超过所涉及的功能。weak_ptr 的主要目的是打破两个对象相互持有引用的引用循环(因此永远不会被释放)。

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

于 2013-07-10T12:46:34.627 回答