
On Wed, Sep 2, 2009 at 1:18 PM, Mateusz Loskot<mateusz@loskot.net> 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@dog:~$ g++ -Wall -pedantic weak_ptr_local.cpp mloskot@dog:~$ ./a.out X dtor 7 deleter mloskot@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.