11

Quoted from C++ Primer $12.1.6:

A weak_ptr (Table 12.5) is a smart pointer that does not control the lifetime of the object to which it points. Instead, a weak_ptr points to an object that is managed by a shared_ptr. Binding a weak_ptr to a shared_ptr does not change the reference count of that shared_ptr. Once the last shared_ptr pointing to the object goes away, the object itself will be deleted. That object will be deleted even if there are weak_ptrs pointing to it—hence the name weak_ptr, which captures the idea that a weak_ptr shares its object “weakly.”

However,I've read an article says:

using make_shared is more efficient. The shared_ptr implementation has to maintain housekeeping information in a control block shared by all shared_ptrs and weak_ptrs referring to a given object. In particular, that housekeeping information has to include not just one but two reference counts:

  1. A “strong reference” count to track the number of shared_ptrs currently keeping the object alive. The shared object is destroyed (and possibly deallocated) when the last strong reference goes away.

  2. A “weak reference” count to track the number of weak_ptrs currently observing the object. The shared housekeeping control block is destroyed and deallocated (and the shared object is deallocated if it was not already) when the last weak reference goes away.

As far as I know,the shared_ptr created by make_shared is in the same control block with those ref countings.So the object will not be released until the last weak_ptr expires.

Question:

  1. Is the Primer wrong? Because weak_ptr will actually affects the lifetime of that object.
  2. Why does the shared_ptr need to track its weak refs?The weak_ptr can tell if the object exists by checking the strong refs in control blocks,so I think the control block does not need to track the weak refs.
  3. Just for curiosity,what does the control block created by shared_ptr look like?Is it something like:

    template<typename T>
    class control_block
    {
       T object;
       size_t strong_refs;
       size_t weak_refs;
       void incre();
       void decre();
       //other member functions...
    };
    //And in shared_ptr:
    template<typename T>
    class shared_ptr
    {
       control_block<T> block;//Is it like this?So that the object and refs are in the same block?
       //member functions...
    };
    
choxsword
  • 3,187
  • 18
  • 44
  • "So the object will not be released until the last weak_ptr expires" That would make the weak_ptr just like a shared_ptr, no? – juanchopanza Mar 31 '18 at 08:36
  • 1
    @juanchopanza That's why I'm confused,as quoted in the second paragraph,the object is in the control block,and it will not be released until last weak_ref expires. – choxsword Mar 31 '18 at 08:38
  • 3
    There's two objects being managed here the `pointed to object` that is deleted when the last shared_ptr is removed and the `control block object` that contains the reference count that is deleted when the last managed pointer to it is deleted (including shared and weak ptrs) – PeterT Mar 31 '18 at 08:39
  • 1
    Destroyed and deallocated are separate events. And there is always a pointer, only 1 less allocation in case of make_shared. – rustyx Mar 31 '18 at 08:39
  • @PeterT But their is no pointer to object if you use `make_shared`,the object is also in the control block. – choxsword Mar 31 '18 at 08:41
  • @PeterT See [this](https://stackoverflow.com/questions/27082860/new-and-make-shared-for-shared-pointers),they do have difference. – choxsword Mar 31 '18 at 08:44
  • 1
    @bigxiao You can call destructor directly. – llllllllll Mar 31 '18 at 08:46
  • @liliscent The word **deleted** used by Primer is misleading,which makes me thinks of **delete ptr**,which will deallocate the memory. – choxsword Mar 31 '18 at 09:08
  • 1
    @bigxiao: to be more precise: `delete ptr` destroys the pointed to object by calling its destructor if it has any, which end the lifetime of the object and then calls operator delete(), which can be specific for the object type, that (by default) deallocates the heap memory that was allocated for the object. – Maarten Hilferink Apr 05 '18 at 09:15

4 Answers4

24

The reference count controls the lifetime of the pointed-to-object. The weak count does not, but does control (or participate in control of) the lifetime of the control block.

If the reference count goes to 0, the object is destroyed, but not necessarily deallocated. When the weak count goes to 0 (or when the reference count goes to 0, if there are no weak_ptrs when that happens), the control block is destroyed and deallocated, and the storage for the object is deallocated if it wasn't already.

The separation between destroying and deallocating the pointed-to-object is an implementation detail you don't need to care about, but it is caused by using make_shared.

If you do

shared_ptr<int> myPtr(new int{10});

you allocate the storage for the int, then pass that into the shared_ptr constructor, which allocates storage for the control block separately. In this case, the storage for the int can be deallocated as early as possible: as soon as the reference count hits 0, even if there is still a weak count.

If you do

auto myPtr = make_shared<int>(10);

then make_shared might perform an optimisation where it allocates the storage for the int and the control block in one go. This means that the storage for the int can't be deallocated until the storage for the control block can also be deallocated. The lifetime of the int ends when the reference count hits 0, but the storage for it is not deallocated until the weak count hits 0.

Is that clear now?

BoBTFish
  • 19,167
  • 3
  • 49
  • 76
  • Why does the control block need to track **weak_refs**?If the `shared_ptr` is created by `new` ,I think there's no need to deallocate the control block when there's no **weak_refs**,because the control block does not take much space. – choxsword Mar 31 '18 at 08:56
  • 1
    I don't see the connection between the **size** of the control block, and whether or not you need to deallocate it?? If you don't deallocate it when there is nothing in your code referring to it, that's a memory leak. Being small doesn't stop it being a leak. – BoBTFish Mar 31 '18 at 09:01
  • 1
    The word **deleted** used by Primer is misleading,which makes me thinks of `delete ptr` that will deallocate the memory. – choxsword Mar 31 '18 at 09:08
  • 1
    What the Primer says is completely true, **if** you ignore the optimisation that `make_shared` is allowed to use. That is, if you assume the storage for the object and the control block are completely separate, then the object will be destroyed and its storage deallocated at the same time. I guess the authors decided you don't need to know about the details of this optimisation - it is only a primer, after all. – BoBTFish Mar 31 '18 at 09:10
  • @bigxiao The Primer seems to conflate two different uses: `shared_ptr p(new T)` and `make_shared()`. `delete ptr` is required to be called, where `ptr` is the argument used during construction of `p`. (This is actually observable by replacing the delete operator.) When `make_shared` is used instead of `new`, the memory allocation is under the control of that function. – curiousguy Jun 12 '18 at 22:48
10

The weak_ptr need to point to something that can tell if the object exist or not so it knows if it can be converted to a shared_ptr. Therefore a small object is needed to housekeep this information.

This housekeeping control block needs to be destroyed when the last week_ptr (or shared_ptr) is removed. Therefore it has to keep count of both the shared_ptr and the week_ptr's.

Note that the housekeeping control block is not the same as the object the ptr's point to and therefore the week_ptr do not affect the objects lifetime.

There is a bunch of different ways to implement smart pointers depending on what behavior you would like it to have. If you want to know more I would recommend "Modern C++ Design" by Alexandrescu (https://www.amazon.com/Modern-Design-Generic-Programming-Patterns/dp/0201704315)

madlers
  • 111
  • 4
  • But the weak_ptr can tell if the object exists by checking the **strong refs** in control blocks,the control block does not need to track the **weak refs**? – choxsword Mar 31 '18 at 08:51
  • 3
    If the weak_ptr is not tracked the control block needs to be deleted then the last shared_ptr is removed. Then the weak_ptr will point to something that do not exist anymore (the control block) and that is not good... – madlers Mar 31 '18 at 08:57
  • If the `shared_ptr` is created by `new` ,I think there's no need to deallocate the control block when there's no weak_refs,because the control block does not take much space. – choxsword Mar 31 '18 at 08:58
  • Now I'm not with you at all... Are you suggesting to have a memory leak of control blocks? – madlers Mar 31 '18 at 09:00
  • 2
    @bigxiao You can't leak objects just because they're small. The control block was allocated, so it must be deallocated. You can run out of memory by leaking lots of small objects just as well as you can by leaking a few large objects. – Miles Budnek Mar 31 '18 at 09:01
  • Thank you,I've mixed something up. – choxsword Mar 31 '18 at 09:01
3

Both weak_ptr and shared_ptr point to memory containing control block. If you delete control block as soon as shared_ptr counter reaches 0 (but weak counter doesn't), you are left with weak_ptrs pointing to garbage memory. Then when you try to use weak_ptr, it reads deallocated memory and bad things happen (UB).

For this reason, control block must be left alive (allocated and constructed, not destroyed nor deallocated) as long as any weak_ptr may try to read it.

Main (pointed-to) object will be destroyed and may (hopefully) be deallocated as soon as shared counter reaches 0. Control block will be destroyed and deallocated when both counters reach 0.

MateuszL
  • 2,751
  • 25
  • 38
0

A nice first step would be making the difference between destruction and de-allocation clear in your mental representation of the concept — which would also be the preferable step over the is-an-implementation-detail-you-don't-need-to-care-about ( kindly alluded ), ignorance-reinforcing step.

So, let SeriousObject be a class, having size approximately half of your system's memory and taking control of the mouse upon construction, and us consider the implied being side-effects of a destructed, yet not de-allocated instance of the SeriousObject in this scenario. In such case, although the mouse control is back, you are still with only half of your memory available. Worst case scenario, somewhere in the code a forgotten, or even worse leaked, weak_ptr exists, leaving your memory with a lame, mood killer and show stopper 50% remainder for the rest of the execution. But hey, at least it's not leaked, right?

Assuming concluding duties here go my suppositions on each of your questions:

  1. Being not actually knowing, I would boldly bet, half my money on the authors having double-checked the text before publifying it, and the other half on the error itself being certainly discovered by now, should it existed in the first place.
  2. Because in that case, wherein the weak_ptr s are not being tracked, shared_ptr s and control block both having been destroyed, and at least one weak_ptr pointing to the object exists, what do you think will happen, the moment when a weak_ptr tries to, as you too so suggested, check the strong refs in the control block? ... Migrains all over the place.
  3. This time, being definitely not knowing, I will say just this: you, as well as I and many other coding-related guys, forget this humble resource, this uh..yea..there's also this—on one hand—definitely not useless to have—on the other—thing, which also happens to be a free, open-source, real-life-case, industry-level-quality knowledge resource. Proceed, brothers! The time has come, the time to seize, what to us, and what by blood and definition, have so always been and so forever shall inviolably be, our at birth delivered right, our through honor enslaving privilege, and our so as to and so beyond death bound duty!

PS ( or more like BTW, actually )

My personal baffling is not with the weak_ptr counting, but with the decision to make such an optimization during that specific stage of the object's lifetime, I'm referring to the in-one-go-allocation-type-optimization and elaborating, what I mean is choosing to optimize the shortest possible, single-time-occuring lifetime stage, while accepting paying such cost with such technical and behavioral side-effects and in exchange taking really up and absolutely effortlessly a handful of goat feces as the reaped fruits of their labors. Pff

Poniros
  • 131
  • 9