我开始研究c++ 11的智能指针,我没有看到std::weak_ptr有任何有用的用途。有人能告诉我什么时候std::weak_ptr是有用的/必要的吗?


当前回答

Here's one example, given to me by @jleahy: Suppose you have a collection of tasks, executed asynchronously, and managed by an std::shared_ptr<Task>. You may want to do something with those tasks periodically, so a timer event may traverse a std::vector<std::weak_ptr<Task>> and give the tasks something to do. However, simultaneously a task may have concurrently decided that it is no longer needed and die. The timer can thus check whether the task is still alive by making a shared pointer from the weak pointer and using that shared pointer, provided it isn't null.

其他回答

我看到了很多有趣的答案,解释引用计数等,但我错过了一个简单的例子,演示如何使用weak_ptr防止内存泄漏。在第一个例子中,我在循环引用的类中使用shared_ptr。当类超出作用域时,它们不会被销毁。

#include<iostream>
#include<memory>
using namespace std;

class B;

class A
{
public:
    shared_ptr<B>bptr;
    A() {
        cout << "A created" << endl;
    }
    ~A() {
        cout << "A destroyed" << endl;
    }
};

class B
{
public:
    shared_ptr<A>aptr;
    B() {
        cout << "B created" << endl;
    }
    ~B() {
        cout << "B destroyed" << endl;
    }
};

int main()
{
    {
        shared_ptr<A> a = make_shared<A>();
        shared_ptr<B> b = make_shared<B>();
        a->bptr = b;
        b->aptr = a;
    }
  // put breakpoint here
}

如果你运行代码片段,你会看到类被创建,但没有被销毁:

A created
B created

现在我们把shared_ptr改成weak_ptr:

class B;
class A
{
public:
    weak_ptr<B>bptr;

    A() {
        cout << "A created" << endl;
    }
    ~A() {
        cout << "A destroyed" << endl;
    }
};

class B
{
public:
    weak_ptr<A>aptr;

    B() {
        cout << "B created" << endl;
    }
    ~B() {
        cout << "B destroyed" << endl;
    }
};

    int main()
    {
        {
            shared_ptr<A> a = make_shared<A>();
            shared_ptr<B> b = make_shared<B>();
            a->bptr = b;
            b->aptr = a;
        }
      // put breakpoint here
    }

这一次,当使用weak_ptr时,我们看到了正确的类破坏:

A created
B created
B destroyed
A destroyed

Std::weak_ptr是解决悬浮指针问题的一个很好的方法。通过使用原始指针,不可能知道所引用的数据是否已被释放。相反,通过让std::shared_ptr管理数据,并将std::weak_ptr提供给数据的用户,用户可以通过调用expired()或lock()来检查数据的有效性。

你不能单独用std::shared_ptr这样做,因为所有std::shared_ptr实例共享数据的所有权,这些数据在std::shared_ptr的所有实例被删除之前没有被删除。下面是一个如何使用lock()检查悬浮指针的例子:

#include <iostream>
#include <memory>

int main()
{
    // OLD, problem with dangling pointer
    // PROBLEM: ref will point to undefined data!

    int* ptr = new int(10);
    int* ref = ptr;
    delete ptr;

    // NEW
    // SOLUTION: check expired() or lock() to determine if pointer is valid

    // empty definition
    std::shared_ptr<int> sptr;

    // takes ownership of pointer
    sptr.reset(new int);
    *sptr = 10;

    // get pointer to data without taking ownership
    std::weak_ptr<int> weak1 = sptr;

    // deletes managed object, acquires new pointer
    sptr.reset(new int);
    *sptr = 5;

    // get pointer to new data without taking ownership
    std::weak_ptr<int> weak2 = sptr;

    // weak1 is expired!
    if(auto tmp = weak1.lock())
        std::cout << "weak1 value is " << *tmp << '\n';
    else
        std::cout << "weak1 is expired\n";
    
    // weak2 points to new data (5)
    if(auto tmp = weak2.lock())
        std::cout << "weak2 value is " << *tmp << '\n';
    else
        std::cout << "weak2 is expired\n";
}

输出

weak1 is expired
weak2 value is 5

当我们不想拥有对象时:

Ex:

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, both指针的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,这更有意义。

另一个答案,希望更简单。(对谷歌员工)

假设您有Team和Member对象。

显然,这是一个关系:Team对象将拥有指向其成员的指针。成员也可能有一个指向他们的Team对象的后向指针。

然后你就有了一个依赖循环。如果您使用shared_ptr,当您放弃对对象的引用时,对象将不再被自动释放,因为它们以循环的方式相互引用。这是内存泄漏。

您可以使用weak_ptr来打破这种情况。“所有者”通常使用shared_ptr,而“所有者”使用weak_ptr来访问父节点,并在需要访问父节点时临时将其转换为shared_ptr。

存储一个弱ptr:

weak_ptr<Parent> parentWeakPtr_ = parentSharedPtr; // automatic conversion to weak from shared

然后在需要的时候使用它

shared_ptr<Parent> tempParentSharedPtr = parentWeakPtr_.lock(); // on the stack, from the weak ptr
if( !tempParentSharedPtr ) {
  // yes, it may fail if the parent was freed since we stored weak_ptr
} else {
  // do stuff
}
// tempParentSharedPtr is released when it goes out of scope

在使用指针时,重要的是要了解可用的不同类型的指针,以及何时使用每种指针是有意义的。指针分为以下两类:

原始指针: 原始指针[即SomeClass* ptrToSomeClass = new SomeClass();] 智能指针: 唯一指针[即std::unique_ptr<SomeClass> uniquePtrToSomeClass (new SomeClass());] 共享指针[即std::shared_ptr<SomeClass> sharedPtrToSomeClass (new SomeClass());] 弱指针[即std::weak_ptr<SomeClass> weakPtrToSomeWeakOrSharedPtr (weakOrSharedPtr);]

Raw pointers (sometimes referred to as "legacy pointers", or "C pointers") provide 'bare-bones' pointer behavior and are a common source of bugs and memory leaks. Raw pointers provide no means for keeping track of ownership of the resource and developers must call 'delete' manually to ensure they are not creating a memory leak. This becomes difficult if the resource is shared as it can be challenging to know whether any objects are still pointing to the resource. For these reasons, raw pointers should generally be avoided and only used in performance-critical sections of the code with limited scope.

Unique pointers are a basic smart pointer that 'owns' the underlying raw pointer to the resource and is responsible for calling delete and freeing the allocated memory once the object that 'owns' the unique pointer goes out of scope. The name 'unique' refers to the fact that only one object may 'own' the unique pointer at a given point in time. Ownership may be transferred to another object via the move command, but a unique pointer can never be copied or shared. For these reasons, unique pointers are a good alternative to raw pointers in the case that only one object needs the pointer at a given time, and this alleviates the developer from the need to free memory at the end of the owning object's lifecycle.

Shared pointers are another type of smart pointer that are similar to unique pointers, but allow for many objects to have ownership over the shared pointer. Like unique pointer, shared pointers are responsible for freeing the allocated memory once all objects are done pointing to the resource. It accomplishes this with a technique called reference counting. Each time a new object takes ownership of the shared pointer the reference count is incremented by one. Similarly, when an object goes out of scope or stops pointing to the resource, the reference count is decremented by one. When the reference count reaches zero, the allocated memory is freed. For these reasons, shared pointers are a very powerful type of smart pointer that should be used anytime multiple objects need to point to the same resource.

最后,弱指针是另一种类型的智能指针,它们不是直接指向资源,而是指向另一个指针(弱指针或共享指针)。弱指针不能直接访问对象,但它们可以判断对象是否仍然存在或是否已经过期。弱指针可以临时转换为共享指针以访问所指向的对象(前提是它仍然存在)。为了说明这一点,考虑下面的例子:

您很忙,会议A和会议B有重叠 你决定去开会A,而你的同事去开会B 你告诉你的同事,如果A会议结束后B会议还在进行,你也会加入 可能会出现以下两种情况: 会议A结束了,会议B还在进行,所以你加入了 会议A已经结束,会议B也已经结束,您不能加入

在本例中,您有一个指向会议B的弱指针。您不是会议B的“所有者”,因此会议B可以在没有您的情况下结束,并且您不知道它是否结束,除非您检查。如果它还没有结束,你可以加入和参与,否则,你不能。这与拥有一个指向会议B的共享指针不同,因为您将同时成为会议a和会议B的“所有者”(同时参与这两个会议)。

The example illustrates how a weak pointer works and is useful when an object needs to be an outside observer, but does not want the responsibility of sharing ownership. This is particularly useful in the scenario that two objects need to point to each other (a.k.a. a circular reference). With shared pointers, neither object can be released because they are still 'strongly' pointed to by the other object. When one of the pointers is a weak pointer, the object holding the weak pointer can still access the other object when needed, provided it still exists.