Ping: abdullah, wandererfan. I hope you guys can read past the first part. I believe you might find the second part interesting. Having shared_ptr in FreeCAD might be a challenge… but IMO it is not as hard as it seems. And it can make many things easier and actually safer.
That being said, tanderson69, it seems to me that there is no decoupling. There is not much purpose in using “new” and not being able to use “delete”. Or worse, using “new” and then destructing with “delete” only when it is correct. The best thing to do, in my opinion, would be to tightly couple object allocation and passing it to a smart pointer. That would mean:
- Never allocate with “new” without passing it to a smart pointer right after it.
- Never construct a smart pointer using a raw pointer. Except in case “1”, just after you have new’d it.
- Never use “delete”.
The std::smart_ptr is just like the Olympic flame. After it was lightened in the Temple of Hera, you only lighten another Olympic Torch by using another already lightened Olympic Torch.
The std::enable_shared_from_this
Now, when an object derives from std::enable_shared_from_this, it carries a weak_ptr to the “control block” shared by a shared_ptr that owns it. A weak_ptr has some properties properties:
- It does not increase the reference counter, so it does not hold the resource from being destructed.
- It has access to the control block and can generate a new shared_ptr for the resource, as long as the reference counter is not zero. A zero counter means that the destruction of the object has already happened or at least, started.
(PS: Probably the control block has two counters. One for the resource deletion and one for the control block deletion. The weak_ptr must increase the control block counter, but not the resource counter.)
In the case of an enable_shared_from_this object, if you abide to the three rules at the beginning of this post, the object will hold a weak_ptr to itself. In this case:
- It is a weak_ptr, so it does not hold itself from being destructed. Otherwise it would be a memory leak.
- The weak_ptr would never be invalid and you would always be able to derive a valid shared_ptr from it. Because there are two ways the weak_ptr can be invalid:
2.1 A shared_ptr was never attributed to it. (we prevent this through policy: always tightly couple allocation and shared_ptr creation… item 1, above)
2.2 The counter reached zero. (in this case, the object was deleted and we are not supposed to access the weak_ptr anyways)
For that reason, enable_shared_from_this::shared_from_this() throws an exception when the weak_ptr is not valid.
Also, we avoid having two “control blocks” for the same resource through policy… item 2. We never create a shared_ptr from a raw pointer.
And we avoid the resource being deleted through policy: never delete this kind of resource… item 3. It would be incorrect to delete it, if you follow item 1.
Role of the enable_shared_from_this
The shared_from_this allows that anyone that has access to the resource might prevent it from being destructed:
- In the present: by holding a shared_ptr.
- In the future: by holding a weak_ptr… in this case, the resource might already be destructed when you try to access it in the future. But as long as it has not been destructed, you can acquire a lock from the weak_ptr to prevent its destruction.
Of course, we can instead implement our own “RequestWeakPtr” kind of method, instead.
Use of raw pointers… normally!
If we opt for shared_ptr, we can still use raw pointers almost everywhere. As long as those raw pointers are held ONLY locally, by functions being called by someone that holds a shared_ptr. Or by some one that is called by someone that is called… (n times)… holds a shared_ptr. The rule is simple:
- You never store a raw pointer. You have to store a weak_ptr.
- You never pass a raw pointer to a different thread.
If you follow those rules, you can use raw pointers normally.
Other advantages
If you use a shared_ptr, you:
- Do not need to wait for certain threads to finish before “releasing a resource”.
This happens, for example, in TechDraw. The destructor for DrawViewPart’ waits for remaining threads to finish. This can block the application!
- Do not need to disconnect signals.
The boost library allows one to pass a weak_ptr to slot::track.
By the use of this mechanism, only possible through the use of some shared_ptr kind:
- The weak_ptr is locked before the slot is executed.
- The object will remain valid until the end of the slot call.
- If the weak_ptr cannot lock the resource (it has been destructed), the connection will be undone.
They call it “Automatic Connection Management”.