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.
Supplement 1: Why can’t we detect whether
shared_ptr
is NULL?Because this is wrong. The equality of
shared_ptr
andnullptr
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 theshared_ptr<T>
object itself exists, but does not point to a validT
.shared_ptr
overloads==
and=
to mean the following:shared_ptr
andnullptr
are equal, indicating whether they are in the empty state;shared_ptr
is assigned the valuenullptr
, which does not mean that theshared_ptr
instance itself is gone, but that the status of thisshared_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 valuableshared_ptr
.If there are two
shared_ptr
pointing to the same instance ofT
in memory at the same time, then any successful evaluation ofshared_ptr
andnullptr
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 certainshared_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 all
shared_ptr
Although it is called a "smart pointer", it is actually not a pointer. A thing defined byshared_ptr<T>
is just a simple variable that stores an instantiatedclass
. It is essentially equivalent to the following code:Why
shared_ptr
can use operators such as*
,&
, and->
like pointers? It’s because theshared_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 judgeint
against anullptr
.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 usesshared_ptr
internally to maintain the lifetime of thevector<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 usingshared_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 aA
pointing to a member withinshared_ptr
, andA
also holds aB
pointing to a member withinshared_ptr
. At this time, the life cycles ofA
andB
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
holdsB
'sshared_ptr
, which means that the life cycle ofB
completely coversA
. 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:
This program uses
WeakReference<IEventListener>
to replace the strong referenceIEventListener
, 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