$include_dir="/home/hyper-archives/boost/include"; include("$include_dir/msg-header.inc") ?>
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.