c++ - weak_ptr这个智能指针有什么用?
高洛峰
高洛峰 2017-04-17 13:05:59
0
2
551

c++primer:

通过使用weak_ptr,不会影响一个给定的strblob所指向的vector的生存期。但是,可以阻止用户访问一个不再存在的vector的企图。

书上说的作用就这一个,我想不通的是,阻止用户访问一个不再存在的vector,难道就不能通过检测strblob的shared_ptr是否为NULL来实现吗?

高洛峰
高洛峰

拥有18年软件开发和IT教学经验。曾任多家上市公司技术总监、架构师、项目经理、高级软件工程师等职务。 网络人气名人讲师,...

reply all(2)
洪涛

Supplement 1: Why can’t we detect whether shared_ptr is NULL?

Because this is wrong. The equality of shared_ptr and nullptr does not mean that the referenced object does not exist.

shared_ptr is allowed to have an "empty" state, which means that the instance of the shared_ptr<T> object itself exists, but does not point to a valid T. shared_ptr overloads == and = to mean the following:

  • shared_ptr and nullptr are equal, indicating whether they are in the empty state;

  • shared_ptr is assigned the value nullptr, which does not mean that the shared_ptr instance itself is gone, but that the status of this shared_ptr instance is changed to empty.

  • A shared_ptr that is already in the empty state can still be assigned to become a valid and valuable shared_ptr.

If there are two shared_ptr pointing to the same instance of T in memory at the same time, then any successful evaluation of shared_ptr and nullptr does not mean that the pointed object no longer exists (has been destroyed) Got it!

In any specific use case, there must be multiple shared_ptr pointing to the same instance (if there are not multiple, why are you sharing). Therefore, it is impossible to determine whether the pointed object has been destroyed by emptying a certain shared_ptr, which has absolutely no possibility of being correct (even "wrong") in terms of semantics and practicality!


Supplement 2: shared_ptrWhat exactly is it?

First of allshared_ptrAlthough it is called a "smart pointer", it is actually not a pointer. A thing defined by shared_ptr<T> is just a simple variable that stores an instantiated class. It is essentially equivalent to the following code:

struct MyClass
{
public:
    int MyValue;
    MyClass(int my_value) {
        MyValue = my_value;
    }
}

void main()
{
    auto a = new MyClass(1); 
    // a 是一个单纯的变量,不是指针
    // a 天然与 main() 函数拥有等同的生命周期
}

Why shared_ptr can use operators such as *, &, and -> like pointers? It’s because the shared_ptr class overloads these operators, allowing programmer to “look” like Just using pointers.

A non-pointer local variable cannot be determined nullptr without operator overloading. Just like you can't judge int against a nullptr.

shared_ptrJudgmentnullptr Due to operator overloading, its essential meaning has changed. It must be analyzed in detail and cannot be taken for granted!


Hmm... Note for non-readers: StrBlob is just an example class in that book, which uses shared_ptr internally to maintain the lifetime of the vector<string> collection itself it manages. It has nothing to do with C++ itself.

Prevent users from accessing a vector that no longer exists. This can be done using shared_ptr, no problem. In fact, in many cases, weak_ptrfull modificationshared_ptr will not explode immediately.

But shared_ptr means that your reference and the original object have a strong connection. If your reference is not unlocked, the original object cannot be destroyed. Abuse of strong connections can easily cause hidden memory leaks in a system that runs for a long time, is relatively large, or is resource-short, which can become a catastrophic problem.

What’s worse, abusing strong ties can lead to disasters like circular references. That is: B holds a A pointing to a member within shared_ptr, and A also holds a B pointing to a member within shared_ptr. At this time, the life cycles of A and B are mutually exclusive. Determined by the other party, in fact neither can be destroyed from memory.
——This is just a simple situation. If there are indirect strong references, or strong references between more than two instances, the resolution of this related bug will be catastrophic.

shared_ptr is a relaxation of the C++ memory management mechanism. But relaxation does not mean that it can be abused, otherwise the final outcome may not be better than applying bare pointers everywhere without releasing them.

Must be clear: Semantically, shared_ptr represents an automatic inference of the life cycle. Its essential meaning is: A holds B's shared_ptr, which means that the life cycle of B completely covers A. To understand it from the level of the tree structure, the pointer holder is the subordinate, and the target pointed by the pointer is the superior - the subordinate is short-lived, but the superior exists forever; if the superior does not exist, the subordinate will not be attached.

In this sense, there are some relationships that you cannot express using shared_ptr:

  • In the affiliation relationship, the relationship is from ancestors to descendants. For example, in an object container, you can use shared_ptr for a specific object to find the container it belongs to, but it cannot be used for the container to find a specific object, because the container cannot require the object to survive longer than itself.

  • Two unrelated objects whose life cycles are not essentially related. For example, in a global event manager (subscriber model), the event subscriber registers itself when it is constructed and deregisters itself when it is destructed. The manager must maintain a pointer to this object (thus sending a message), but it is absolutely not allowed to interfere with the life cycle of the object. The destruction of the object needs to ignore the existence of subscribers and is only controlled by strong references necessary for other businesses.

This is where weak_ptr comes in handy. weak_ptrProvide a smart pointer that (1) can determine whether the other party is alive (2) has no interference with each other's life cycle (3) can temporarily borrow a strong reference (to ensure the survival of the other party in the short period of time when you need to reference the other party) .

And weak_ptr requires programmers to determine survival and locking at runtime, which is also a logically necessary intrinsic complexity - if others live shorter than you, of course you have to: (1) Determine others first Life and Death (2) If he is still alive, give him life extensions until you run out of them.


In fact, the concepts of weak reference and strong reference are also a problem that needs attention in languages ​​with GC. Take C# as an example:

public class EventDisposer
{
    private Dictionary<int, WeakReference<IEventListener>> pendingEvents;
    private void Dispose(int event_id)
    {
        var reference = pendingEvents[event_id];
        IEventListener context;
        if (reference.TryGetTarget(out context)) 
        {
            // context is valid from here to the end of function
            context.Trigger();
        }
        else
        {
            // context has already been destroyed...
        }
    }
}

This program uses WeakReference<IEventListener> to replace the strong reference IEventListener, so that the object receiving event distribution can be completely destroyed during the life cycle without any inevitable connection with the event distributor.

伊谢尔伦

Prevent circular references

Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template