c++primer:
通过使用weak_ptr,不会影响一个给定的strblob所指向的vector的生存期。但是,可以阻止用户访问一个不再存在的vector的企图。
书上说的作用就这一个,我想不通的是,阻止用户访问一个不再存在的vector,难道就不能通过检测strblob的shared_ptr是否为NULL来实现吗?
PHP中文网2017-04-17 13:07:59
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_ptr
What exactly is it?
First of allshared_ptr
Although 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_ptr
Judgmentnullptr
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_ptr
full 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_ptr
Provide 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.