Boost logo

Boost :

Subject: Re: [boost] Weak pointer to object not managed by a shared_ptr
From: OvermindDL1 (overminddl1_at_[hidden])
Date: 2009-09-02 21:48:50


On Wed, Sep 2, 2009 at 1:18 PM, Mateusz Loskot<mateusz_at_[hidden]> wrote:
> Ryan Gallagher wrote:
>> Mateusz Loskot <mateusz <at> loskot.net> writes:
>>> I'm wondering what would be practical use case of that?
>>
>> I think the use case is for when you have an object that needs to
>> reference an X instance (from your example) but doesn't have any
>> knowledge of the lifetime of that X instance.  Thus, instead of
>> using  a C++ reference or pointer that object can hold a
>> weak_ptr<X> -- enabling it to check if the X instance is still
>> alive when it needs to use it.
>
> I see. IOW, the idea is to make a weak association between objects
> and avoid crashes caused by dereferencing invalid pointer.
>
>>> Is it a part of any of known idioms or patterns?
>>
>> I don't think so. I think this use-case (referencing but not knowing
>> the lifetime) is something that should probably be avoided in one's
>> design.  However, if it's impractical to do so then this would
>> just make it safer than using a raw pointer or C++ reference.
>
> Right.
>
>>> Also, is it valid to assume that this technique can extend lifetime of X
>>> so of the internal integral X::i_ ?
>>
>> No. The example you have is undefined behavior as the X instance
>> is destructed when it goes out of scope.
>
> The confirmation of UB is enough for me, however...
>
>> Your just using the
>> shared_ptr<X> to reference the destroyed X instance.  This is the
>> same as if you had held a raw pointer to that X instance after it
>> was destructed.
>
> simple testes using GCC 4.3 and Visual C++ 9.0 show that destruction
> occurs in different point than we expect:
>
> 1) I added verbose destructor to X
>
> ~X() { cout << "X dtor\n"; }
>
> 2) Compiled and executed the following use case of X:
>
> shared_ptr<X> spx;
> {
>    X x1(7);
>    spx = shared_ptr<X>(x1.get_weak_ptr());
> } // *** x1 is being destructed
> cout << spx->get_i() << std::endl;
>
>
> mloskot_at_dog:~$ g++ -Wall -pedantic weak_ptr_local.cpp
> mloskot_at_dog:~$ ./a.out
> X dtor
> 7
> deleter
> mloskot_at_dog:~$
>
> So, x1 is destructed but its value of 7 is still accessible.
> I assume I'm observing UB here. I understand nature of UB and that
> it can give unexpectedly 'correct' results.
>
> Shortly, this is UB and one should always assume the point of x1
> object destruction (marked with ***) is when the execution
> leaves the scope in which x1 was declared.
>
> Is this correct?
>
>> The whole point is to just hold a weak_ptr<X> until you actually
>> need to use it.  At that point you can check whether the X instance
>> is still alive.
>
> So, the weak_ptr acts as a flag indicating if pointee is still
> alive (a self-indicator). Alternative would be to cache some boolean
> indicator and check it instead of the weak_ptr, before accessing
> object of X.
>
>>>         shared_ptr<X> spx;
>>>         {
>>>             X x1(7);
>>>
>>>             // XXX: lifetime is extended here?
>>>             spx = shared_ptr<X>(x1.get_weak_ptr());
>>>         }
>>>
>>>         // XXX: No exception. x1 is still alive, so the pointer valid?
>>>         cout << spx->get_i() << std::endl;
>>
>> x1 is not alive, it was destructed at scope exit.
>
> Yes, this is clear.
>
>> The shared_ptr<X> that it held was also destructed at this point.
>> You just kept the ref-count up through spx even though it refers
>> to a destroyed object.  Check the ref-count at this point, it
>> should be 1.
>
> Yes, however get_i() returns the value stored in x1. It's UB anyway.
>
>> For using this, think about X lifetimes being managed by some other
>> class (XManager).  Think about another class A that isn't managed
>> my XManager but needs to weakly reference some X instance.
>
> Yes, I understand this kind of use cases. It makes sense to me.
>
>> Perhaps someone else can give a better, more realistic, example
>> though, as even this one I wouldn't code this way.
>
> I wouldn't code this way too.
>
> Thanks all for helping me to understand it better!

Bah, x1 exists on the stack, its memory is not free'd after its
destructor is run, hence when you access get_i, it still return 7.
Try adding this right before that ->get_i call:
char buf_on_stack[255]; // or however large X is, plus some for just in case...
memset(&buf_on_stack, 255, size_of(buf_on_stack));

Then notice that your get_i call does not return 7, rather it returns
part of that array, which is filled with all 1's.


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk