std::weak_ptr在什么情况下有用?

364
我开始学习C++11的智能指针,但我没有看到std::weak_ptr的任何有用的用途。有人可以告诉我什么时候使用std::weak_ptr是有用/必要的吗?

15个回答

6

我将std::weak_ptr<T>看作是对std::shared_ptr<T>的一个句柄:它允许我在std::shared_ptr<T>仍然存在时获取它,但不会延长其生命周期。有几种情况下这样的观点是有用的:

// Some sort of image; very expensive to create.
std::shared_ptr< Texture > texture;

// A Widget should be able to quickly get a handle to a Texture. On the
// other hand, I don't want to keep Textures around just because a widget
// may need it.

struct Widget {
    std::weak_ptr< Texture > texture_handle;
    void render() {
        if (auto texture = texture_handle.get(); texture) {
            // do stuff with texture. Warning: `texture`
            // is now extending the lifetime because it
            // is a std::shared_ptr< Texture >.
        } else {
            // gracefully degrade; there's no texture.
        }
    }
};

另一个重要的场景是打破数据结构中的循环。

// Asking for trouble because a node owns the next node, and the next node owns
// the previous node: memory leak; no destructors automatically called.
struct Node {
    std::shared_ptr< Node > next;
    std::shared_ptr< Node > prev;
};

// Asking for trouble because a parent owns its children and children own their
// parents: memory leak; no destructors automatically called.
struct Node {
    std::shared_ptr< Node > parent;
    std::shared_ptr< Node > left_child;
    std::shared_ptr< Node > right_child;
};

// Better: break dependencies using a std::weak_ptr (but not best way to do it;
// see Herb Sutter's talk).
struct Node {
    std::shared_ptr< Node > next;
    std::weak_ptr< Node > prev;
};

// Better: break dependencies using a std::weak_ptr (but not best way to do it;
// see Herb Sutter's talk).
struct Node {
    std::weak_ptr< Node > parent;
    std::shared_ptr< Node > left_child;
    std::shared_ptr< Node > right_child;
};

Herb Sutter有一个精彩的演讲,解释了最佳语言特性(在这种情况下是智能指针)的使用,以确保默认无泄漏(意思是:通过构造一切都自然而然地嵌入在一起;你几乎不可能搞砸)。这是必看的。


2

当我们不想拥有对象时:

例如:

class A
{
    shared_ptr<int> sPtr1;
    weak_ptr<int> wPtr1;
}

在上述类中,wPtr1 不拥有 wPtr1 指向的资源。如果该资源被删除,则 wPtr1 会过期。 为避免循环依赖:
shard_ptr<A> <----| shared_ptr<B> <------
    ^             |          ^          |
    |             |          |          |
    |             |          |          |
    |             |          |          |
    |             |          |          |
class A           |     class B         |
    |             |          |          |
    |             ------------          |
    |                                   |
    -------------------------------------

现在如果我们将类B和A的shared_ptr进行关联,那么两个指针的use_count都会变成2。
当shared_ptr超出其作用域时,计数仍然保持为1,因此A和B对象不会被删除。
class B;

class A
{
    shared_ptr<B> sP1; // use weak_ptr instead to avoid CD

public:
    A() {  cout << "A()" << endl; }
    ~A() { cout << "~A()" << endl; }

    void setShared(shared_ptr<B>& p)
    {
        sP1 = p;
    }
};

class B
{
    shared_ptr<A> sP1;

public:
    B() {  cout << "B()" << endl; }
    ~B() { cout << "~B()" << endl; }

    void setShared(shared_ptr<A>& p)
    {
        sP1 = p;
    }
};

int main()
{
    shared_ptr<A> aPtr(new A);
    shared_ptr<B> bPtr(new B);

    aPtr->setShared(bPtr);
    bPtr->setShared(aPtr);

    return 0;  
}

输出:

A()
B()

从输出中可以看出,A和B指针从未被删除,因此存在内存泄漏问题。

为了避免这样的问题,只需在类A中使用weak_ptr而不是shared_ptr更为明智。


2

受 @offirmo 回答的启发,我编写了这段代码并运行了 Visual Studio 诊断工具:

#include <iostream>
#include <vector>
#include <memory>

using namespace std;

struct Member;
struct Team;

struct Member {
    int x = 0;

    Member(int xArg) {
        x = xArg;
    }

    shared_ptr<Team> teamPointer;
};

struct Team {
    vector<shared_ptr<Member>> members;
};

void foo() {
    auto t1 = make_shared<Team>();
    for (int i = 0; i < 1000000; i++) {
        t1->members.push_back(make_shared<Member>(i));
        t1->members.back()->teamPointer = t1;
    }
}

int main() {
    foo();

    while (1);

    return 0;
}

当成员指针指向团队时,使用 shared_ptr teamPointer 后,内存不会在foo()结束后被释放,即内存占用量保持在约150MB左右。

但是,如果将其更改为诊断工具中的 weak_ptr teamPointer ,则会出现一个峰值,然后内存使用量返回到约2MB左右。


2

共享指针存在一个缺陷:

共享指针无法处理父子循环依赖。也就是说,如果父类使用共享指针使用子类的对象,在同一文件中,如果子类使用父类的对象,共享指针将无法销毁所有对象,即使在循环依赖场景下共享指针根本不会调用析构函数。基本上,共享指针不支持引用计数机制。

我们可以使用弱指针来克服这个缺点。


弱引用如何处理循环依赖? - curiousguy
1
@curiousguy,如果一个子对象使用弱引用指向父对象,那么当没有其他共享(强)引用指向父对象时,父对象可能会被释放。因此,通过子对象访问父对象时,必须测试弱引用以查看父对象是否仍然可用。或者,为了避免额外的条件,可以使用循环引用跟踪机制(标记-清除或在引用计数减少时进行探测,这两种方法都具有不良的渐近性能),当父对象和子对象之间的唯一共享引用来自彼此时,可以打破循环共享引用。 - Shelby Moore III
@ShelbyMooreIII "必须测试以查看父级是否仍然可用"是的,并且您必须能够正确地对不可用情况做出反应!这种情况在真正(即强)引用中不会发生。这意味着弱引用不是一种替代品:它需要改变逻辑。 - curiousguy
2
@curiousguy,你没有问:“weak_ptr如何作为shared_ptr的替代品处理循环依赖关系而不改变程序逻辑?” :-) - Shelby Moore III

2

http://en.cppreference.com/w/cpp/memory/weak_ptr std::weak_ptr 是一个智能指针,它持有一个非拥有("弱")引用到由 std::shared_ptr 管理的对象。必须将其转换为 std::shared_ptr 才能访问被引用的对象。

std::weak_ptr 模型化了临时所有权:当只有在对象存在时才需要访问它,并且它可能随时被其他人删除时,使用 std::weak_ptr 跟踪对象,并将其转换为 std::shared_ptr 来承担临时所有权。如果此时原始的 std::shared_ptr 被销毁,则对象的生命周期将延长,直到临时 std::shared_ptr 也被销毁为止。

此外,std::weak_ptr 用于打破 std::shared_ptr 的循环引用。


如何打破循环引用? - curiousguy
@curiousguy Rainer Grimm在解释这些循环引用及如何使用std::weak_ptr打破它们方面做得相当不错: "Back to Basics: Smart Pointers - Rainer Grimm - CppCon 2020" -- https://youtu.be/sQCSX7vmmKY?t=2496 (如果视频从开头开始,则跳至41:36) - Milan
这只是链接的复制粘贴,人们可以自己找到它。它没有任何添加价值。 - underscore_d

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接