[shared_ptr] Interlocked* - possible bug?

Hi Peter, I think there is a bug in your Interlocked* implementation of shared_ptr (http://www.pdimov.com/cpp/shared_count_x86_exp2.hpp). In atomic_read you have: inline long atomic_read(long volatile const & value) { return value; } I don't believe this is thread-safe. In order to make sure you're receiving the latest value, I assume you could do something like: inline long atomic_read(long volatile & value) { const int IMPOSSIBLE_VALUE = -100; return InterlockedCompareExchange(&value, IMPOSSIBLE_VALUE, IMPOSSIBLE_VALUE); } Am I missing something? Best, John -- John Torjo -- john@torjo.com Contributing editor, C/C++ Users Journal -- "Win32 GUI Generics" -- generics & GUI do mix, after all -- http://www.torjo.com/win32gui/ -- v1.4 - save_dlg - true binding of your data to UI controls! + easily add validation rules (win32gui/examples/smart_dlg)

John Torjo wrote:
Hi Peter,
I think there is a bug in your Interlocked* implementation of shared_ptr (http://www.pdimov.com/cpp/shared_count_x86_exp2.hpp).
In atomic_read you have: inline long atomic_read(long volatile const & value) { return value; }
I don't believe this is thread-safe. In order to make sure you're receiving the latest value, I assume you could do something like:
inline long atomic_read(long volatile & value) { const int IMPOSSIBLE_VALUE = -100; return InterlockedCompareExchange(&value, IMPOSSIBLE_VALUE, IMPOSSIBLE_VALUE); }
Here are my two cents: On other processors, a compiler could make the assumption that unless it sees an explicit memory barrier (or some code which it cannot analyse) it can just cache the value of a variable (whether or not declared as volatile). On x86 you don't need to assert any memory barriers to ensure visibility of data, hence the compiler should assume that any data declared as volatile could potentially change without it knowing so, and hence should reload any such data each time. So I think the above should be safe on the x86. Tom

Tomas Puverle wrote:
Here are my two cents:
On other processors, a compiler could make the assumption that unless it sees an explicit memory barrier (or some code which it cannot analyse) it can just cache the value of a variable (whether or not declared as volatile).
(repost) No, the C++ standard does not grant the compiler such freedom, although the MMU can certainly use a cached copy. ;-)

John Torjo wrote:
Hi Peter,
I think there is a bug in your Interlocked* implementation of shared_ptr (http://www.pdimov.com/cpp/shared_count_x86_exp2.hpp).
In atomic_read you have: inline long atomic_read(long volatile const & value) { return value; }
I don't believe this is thread-safe.
(repost) On x86/IA32, I believe that it's as thread safe as you can get. Note that all count updates go through Interlocked* calls. On IA32, plain reads can only return a somewhat "surprising" value when interleaved with plain writes, and even then, the element of surprise is limited. ;-)

Peter Dimov wrote:
John Torjo wrote:
Hi Peter,
I think there is a bug in your Interlocked* implementation of shared_ptr (http://www.pdimov.com/cpp/shared_count_x86_exp2.hpp).
In atomic_read you have: inline long atomic_read(long volatile const & value) { return value; }
I don't believe this is thread-safe.
(repost)
On x86/IA32, I believe that it's as thread safe as you can get. Note that all count updates go through Interlocked* calls. On IA32, plain reads can only return a somewhat "surprising" value when interleaved with plain writes, and even then, the element of surprise is limited. ;-)
I kind of wonder. Here's my test scenario, which I think is quite effective ;) The main thread creates a window. Then it internally has a shared pointer to it. Other threads might want access to this window. Each other thread keeps a weak_pointer to it. Whenever a thread (other than main) wants to access this window, it will query the weak_pointer. The weak_pointer needs to know the LATEST reference count in order to know if the weak pointer is still valid. Thus, atomic_read that simply returns the value is not enough. Best, John -- John Torjo -- john@torjo.com Contributing editor, C/C++ Users Journal -- "Win32 GUI Generics" -- generics & GUI do mix, after all -- http://www.torjo.com/win32gui/ -- v1.4 - save_dlg - true binding of your data to UI controls! + easily add validation rules (win32gui/examples/smart_dlg)

John Torjo wrote: [...]
Whenever a thread (other than main) wants to access this window, it will query the weak_pointer. The weak_pointer needs to know the LATEST reference count in order to know if the weak pointer is still valid. Thus, atomic_read that simply returns the value is not enough.
No barriers needed for its lock() call (and shared_ptr-from-weak_ptr ctor). It can't see 'false' zero unless you violate the "basic" thread- safety contract. Value consistency is ensured by conditional increment using 'naked' (w/o any barriers) CAS (LL/LR-SC aside for a moment) IFF the observed 'old' value is not zero. Also, < Forward Inline > -------- Original Message -------- Message-ID: <415446A0.F2BD31CF@web.de> Newsgroups: comp.lang.c++.moderated Subject: Re: Vindicated? Sutter and COW Strings References: ... <slrncl63k0.pk.ben-public-nospam@decadentplace.org.uk> Ben Hutchings wrote: [...]
The odd thing is that there is no obvious way to read with an acquire or any other kind of memory barrier. It is possible to do this using InterlockedCompareExchange with the exchange and comparand set to the same value, preferably an unlikely one to avoid invalidating cache lines. (John Torjo suggested this on the Boost mailing list.) This provides a full memory barrier; recent versions of Windows have variants of the function that provide a acquire or release barrier.
Take a closer look at MS docu. See also: http://google.com/groups?threadm=XIRDc.192766%24Ly.83841%40attbi_s01 (Subject: Re: DCSI - thread safe singleton) I mean followups too. regards, alexander.

Alexander Terekhov wrote:
John Torjo wrote: [...]
Whenever a thread (other than main) wants to access this window, it will query the weak_pointer. The weak_pointer needs to know the LATEST reference count in order to know if the weak pointer is still valid. Thus, atomic_read that simply returns the value is not enough.
No barriers needed for its lock() call (and shared_ptr-from-weak_ptr ctor). It can't see 'false' zero unless you violate the "basic" thread- safety contract. Value consistency is ensured by conditional increment
which is?
using 'naked' (w/o any barriers) CAS (LL/LR-SC aside for a moment) IFF the observed 'old' value is not zero.
(after about 3 hours of reading different threads on google) I still think I'm right ;) Here's my scenario: (remember - we're talking about: http://www.pdimov.com/cpp/shared_count_x86_exp2.hpp) class test { ... }; [thread1] shared_ptr<test> p1( new test); // refcount = 1 ---------------- thread switch [thread2] shared_ptr<test> p2(p1); // refcount = 2 weak_ptr<test> weak(p2); p2 = shared_ptr<test>(); // refcount = 1 ------------------thread switch [thread1] p1 = shared_ptr<test>(); // refcount = 0 - object gets destroyed ---------------- thread switch [thread2] // here - you need InterlockedCompareExchange // to realize that refcount is 0 (zero) shared_ptr<test> p3 = weak.lock(); Also, see my other post to Peter Dimov. Best, John -- John Torjo -- john@torjo.com Contributing editor, C/C++ Users Journal -- "Win32 GUI Generics" -- generics & GUI do mix, after all -- http://www.torjo.com/win32gui/ -- v1.4 - save_dlg - true binding of your data to UI controls! + easily add validation rules (win32gui/examples/smart_dlg)

John Torjo wrote: [...]
// here - you need InterlockedCompareExchange // to realize that refcount is 0 (zero) shared_ptr<test> p3 = weak.lock();
lock() calls InterlockedCompareExchange (too bad it's fully fenced) if refcount isn't 0. Again, it can't see "false" zero. regards, alexander.

Alexander Terekhov wrote:
John Torjo wrote: [...]
// here - you need InterlockedCompareExchange // to realize that refcount is 0 (zero) shared_ptr<test> p3 = weak.lock();
lock() calls InterlockedCompareExchange (too bad it's fully fenced) if refcount isn't 0. Again, it can't see "false" zero.
of course - with the change I suggested ;) I wanted to say that the original implementation (http://www.pdimov.com/cpp/shared_count_x86_exp2.hpp) has a bug. Take a look at it please. Best, John -- John Torjo -- john@torjo.com Contributing editor, C/C++ Users Journal -- "Win32 GUI Generics" -- generics & GUI do mix, after all -- http://www.torjo.com/win32gui/ -- v1.4 - save_dlg - true binding of your data to UI controls! + easily add validation rules (win32gui/examples/smart_dlg)

John Torjo wrote: [...]
lock() calls InterlockedCompareExchange (too bad it's fully fenced) if refcount isn't 0. Again, it can't see "false" zero.
of course - with the change I suggested ;)
Really?
I wanted to say that the original implementation (http://www.pdimov.com/cpp/shared_count_x86_exp2.hpp) has a bug. Take a look at it please.
The site seems to be inaccessible. Are you sure that its lock() doesn't call InterlockedCompareExchange when it sees not expired refcount? regards, alexander.

I wanted to say that the original implementation (http://www.pdimov.com/cpp/shared_count_x86_exp2.hpp) has a bug. Take a look at it please.
The site seems to be inaccessible. Are you sure that its lock() doesn't call InterlockedCompareExchange when it sees not expired refcount?
Just to make sure, I paste here the code: ----------------------------------- #ifndef BOOST_DETAIL_SHARED_COUNT_HPP_INCLUDED #define BOOST_DETAIL_SHARED_COUNT_HPP_INCLUDED #if defined(_MSC_VER) && (_MSC_VER >= 1020) # pragma once #endif // // detail/shared_count_x86_exp.hpp // // An experimental version of shared_count.hpp that uses x86-32 // atomic instructions (the Interlocked* family) // // Copyright (c) 2001, 2002, 2003 Peter Dimov and Multi Media Ltd. // // Permission to copy, use, modify, sell and distribute this software // is granted provided this copyright notice appears in all copies. // This software is provided "as is" without express or implied // warranty, and with no claim as to its suitability for any purpose. // #include <boost/config.hpp> #if defined(BOOST_SP_USE_STD_ALLOCATOR) && defined(BOOST_SP_USE_QUICK_ALLOCATOR) # error BOOST_SP_USE_STD_ALLOCATOR and BOOST_SP_USE_QUICK_ALLOCATOR are incompatible. #endif #include <boost/checked_delete.hpp> #include <boost/throw_exception.hpp> #if defined(BOOST_SP_USE_QUICK_ALLOCATOR) #include <boost/detail/quick_allocator.hpp> #endif #include <memory> // std::auto_ptr, std::allocator #include <functional> // std::less #include <exception> // std::exception #include <new> // std::bad_alloc #include <typeinfo> // std::type_info in get_deleter #include <cstddef> // std::size_t #ifdef __BORLANDC__ # pragma warn -8026 // Functions with excep. spec. are not expanded inline # pragma warn -8027 // Functions containing try are not expanded inline #endif // Interlocked* extern "C" { long __cdecl _InterlockedIncrement(long volatile * destination); long __cdecl _InterlockedDecrement(long volatile * destination); long __cdecl _InterlockedCompareExchange(long volatile * destination, long exchange, long comparand); } #pragma intrinsic(_InterlockedIncrement) #pragma intrinsic(_InterlockedDecrement) #pragma intrinsic(_InterlockedCompareExchange) namespace boost { // Debug hooks #if defined(BOOST_SP_ENABLE_DEBUG_HOOKS) void sp_scalar_constructor_hook(void * px, std::size_t size, void * pn); void sp_array_constructor_hook(void * px); void sp_scalar_destructor_hook(void * px, std::size_t size, void * pn); void sp_array_destructor_hook(void * px); #endif // The standard library that comes with Borland C++ 5.5.1 // defines std::exception and its members as having C calling // convention (-pc). When the definition of bad_weak_ptr // is compiled with -ps, the compiler issues an error. // Hence, the temporary #pragma option -pc below. The version // check is deliberately conservative. #if defined(__BORLANDC__) && __BORLANDC__ == 0x551 # pragma option push -pc #endif class bad_weak_ptr: public std::exception { public: virtual char const * what() const throw() { return "boost::bad_weak_ptr"; } }; #if defined(__BORLANDC__) && __BORLANDC__ == 0x551 # pragma option pop #endif namespace detail { // inline long atomic_increment(long volatile & value) { return _InterlockedIncrement(&value); } inline long atomic_decrement(long volatile & value) { return _InterlockedDecrement(&value); } inline long atomic_read(long volatile const & value) { return value; } inline long atomic_conditional_increment(long volatile & value) { for(;;) { long tmp = value; if(tmp == 0) return 0; if(_InterlockedCompareExchange(&value, tmp + 1, tmp) == tmp) return tmp + 1; } } // class sp_counted_base { public: sp_counted_base(): use_count_(1), weak_count_(1) { } virtual ~sp_counted_base() // nothrow { } // dispose() is called when use_count_ drops to zero, to release // the resources managed by *this. virtual void dispose() = 0; // nothrow // destruct() is called when weak_count_ drops to zero. virtual void destruct() // nothrow { delete this; } virtual void * get_deleter(std::type_info const & ti) = 0; void add_ref_copy() { atomic_increment(use_count_); } void add_ref_lock() { if(atomic_conditional_increment(use_count_) == 0) boost::throw_exception(boost::bad_weak_ptr()); } void release() // nothrow { if(atomic_decrement(use_count_) == 0) { dispose(); weak_release(); } } void weak_add_ref() // nothrow { atomic_increment(weak_count_); } void weak_release() // nothrow { if(atomic_decrement(weak_count_) == 0) { destruct(); } } long use_count() const // nothrow { return atomic_read(use_count_); } private: sp_counted_base(sp_counted_base const &); sp_counted_base & operator= (sp_counted_base const &); long use_count_; long weak_count_; // weak_count + (use_count == 0) }; #if defined(BOOST_SP_ENABLE_DEBUG_HOOKS) template<class T> void cbi_call_constructor_hook(sp_counted_base * pn, T * px, checked_deleter<T> const &, int) { boost::sp_scalar_constructor_hook(px, sizeof(T), pn); } template<class T> void cbi_call_constructor_hook(sp_counted_base *, T * px, checked_array_deleter<T> const &, int) { boost::sp_array_constructor_hook(px); } template<class P, class D> void cbi_call_constructor_hook(sp_counted_base *, P const &, D const &, long) { } template<class T> void cbi_call_destructor_hook(sp_counted_base * pn, T * px, checked_deleter<T> const &, int) { boost::sp_scalar_destructor_hook(px, sizeof(T), pn); } template<class T> void cbi_call_destructor_hook(sp_counted_base *, T * px, checked_array_deleter<T> const &, int) { boost::sp_array_destructor_hook(px); } template<class P, class D> void cbi_call_destructor_hook(sp_counted_base *, P const &, D const &, long) { } #endif // // Borland's Codeguard trips up over the -Vx- option here: // #ifdef __CODEGUARD__ # pragma option push -Vx- #endif template<class P, class D> class sp_counted_base_impl: public sp_counted_base { private: P ptr; // copy constructor must not throw D del; // copy constructor must not throw sp_counted_base_impl(sp_counted_base_impl const &); sp_counted_base_impl & operator= (sp_counted_base_impl const &); typedef sp_counted_base_impl<P, D> this_type; public: // pre: initial_use_count <= initial_weak_count, d(p) must not throw sp_counted_base_impl(P p, D d): ptr(p), del(d) { #if defined(BOOST_SP_ENABLE_DEBUG_HOOKS) detail::cbi_call_constructor_hook(this, p, d, 0); #endif } virtual void dispose() // nothrow { #if defined(BOOST_SP_ENABLE_DEBUG_HOOKS) detail::cbi_call_destructor_hook(this, ptr, del, 0); #endif del(ptr); } virtual void * get_deleter(std::type_info const & ti) { return ti == typeid(D)? &del: 0; } #if defined(BOOST_SP_USE_STD_ALLOCATOR) void * operator new(std::size_t) { return std::allocator<this_type>().allocate(1, static_cast<this_type *>(0)); } void operator delete(void * p) { std::allocator<this_type>().deallocate(static_cast<this_type *>(p), 1); } #endif #if defined(BOOST_SP_USE_QUICK_ALLOCATOR) void * operator new(std::size_t) { return quick_allocator<this_type>::alloc(); } void operator delete(void * p) { quick_allocator<this_type>::dealloc(p); } #endif }; #if defined(BOOST_SP_ENABLE_DEBUG_HOOKS) int const shared_count_id = 0x2C35F101; int const weak_count_id = 0x298C38A4; #endif class weak_count; class shared_count { private: sp_counted_base * pi_; #if defined(BOOST_SP_ENABLE_DEBUG_HOOKS) int id_; #endif friend class weak_count; public: shared_count(): pi_(0) // nothrow #if defined(BOOST_SP_ENABLE_DEBUG_HOOKS) , id_(shared_count_id) #endif { } template<class P, class D> shared_count(P p, D d): pi_(0) #if defined(BOOST_SP_ENABLE_DEBUG_HOOKS) , id_(shared_count_id) #endif { #ifndef BOOST_NO_EXCEPTIONS try { pi_ = new sp_counted_base_impl<P, D>(p, d); } catch(...) { d(p); // delete p throw; } #else pi_ = new sp_counted_base_impl<P, D>(p, d); if(pi_ == 0) { d(p); // delete p boost::throw_exception(std::bad_alloc()); } #endif } #ifndef BOOST_NO_AUTO_PTR // auto_ptr<Y> is special cased to provide the strong guarantee template<class Y> explicit shared_count(std::auto_ptr<Y> & r): pi_(new sp_counted_base_impl< Y *, checked_deleter<Y> >(r.get(), checked_deleter<Y>())) #if defined(BOOST_SP_ENABLE_DEBUG_HOOKS) , id_(shared_count_id) #endif { r.release(); } #endif ~shared_count() // nothrow { if(pi_ != 0) pi_->release(); #if defined(BOOST_SP_ENABLE_DEBUG_HOOKS) id_ = 0; #endif } shared_count(shared_count const & r): pi_(r.pi_) // nothrow #if defined(BOOST_SP_ENABLE_DEBUG_HOOKS) , id_(shared_count_id) #endif { if(pi_ != 0) pi_->add_ref_copy(); } explicit shared_count(weak_count const & r); // throws bad_weak_ptr when r.use_count() == 0 shared_count & operator= (shared_count const & r) // nothrow { sp_counted_base * tmp = r.pi_; if(tmp != 0) tmp->add_ref_copy(); if(pi_ != 0) pi_->release(); pi_ = tmp; return *this; } void swap(shared_count & r) // nothrow { sp_counted_base * tmp = r.pi_; r.pi_ = pi_; pi_ = tmp; } long use_count() const // nothrow { return pi_ != 0? pi_->use_count(): 0; } bool unique() const // nothrow { return use_count() == 1; } friend inline bool operator==(shared_count const & a, shared_count const & b) { return a.pi_ == b.pi_; } friend inline bool operator<(shared_count const & a, shared_count const & b) { return std::less<sp_counted_base *>()(a.pi_, b.pi_); } void * get_deleter(std::type_info const & ti) const { return pi_? pi_->get_deleter(ti): 0; } }; #ifdef __CODEGUARD__ # pragma option pop #endif class weak_count { private: sp_counted_base * pi_; #if defined(BOOST_SP_ENABLE_DEBUG_HOOKS) int id_; #endif friend class shared_count; public: weak_count(): pi_(0) // nothrow #if defined(BOOST_SP_ENABLE_DEBUG_HOOKS) , id_(weak_count_id) #endif { } weak_count(shared_count const & r): pi_(r.pi_) // nothrow #if defined(BOOST_SP_ENABLE_DEBUG_HOOKS) , id_(shared_count_id) #endif { if(pi_ != 0) pi_->weak_add_ref(); } weak_count(weak_count const & r): pi_(r.pi_) // nothrow #if defined(BOOST_SP_ENABLE_DEBUG_HOOKS) , id_(shared_count_id) #endif { if(pi_ != 0) pi_->weak_add_ref(); } ~weak_count() // nothrow { if(pi_ != 0) pi_->weak_release(); #if defined(BOOST_SP_ENABLE_DEBUG_HOOKS) id_ = 0; #endif } weak_count & operator= (shared_count const & r) // nothrow { sp_counted_base * tmp = r.pi_; if(tmp != 0) tmp->weak_add_ref(); if(pi_ != 0) pi_->weak_release(); pi_ = tmp; return *this; } weak_count & operator= (weak_count const & r) // nothrow { sp_counted_base * tmp = r.pi_; if(tmp != 0) tmp->weak_add_ref(); if(pi_ != 0) pi_->weak_release(); pi_ = tmp; return *this; } void swap(weak_count & r) // nothrow { sp_counted_base * tmp = r.pi_; r.pi_ = pi_; pi_ = tmp; } long use_count() const // nothrow { return pi_ != 0? pi_->use_count(): 0; } friend inline bool operator==(weak_count const & a, weak_count const & b) { return a.pi_ == b.pi_; } friend inline bool operator<(weak_count const & a, weak_count const & b) { return std::less<sp_counted_base *>()(a.pi_, b.pi_); } }; inline shared_count::shared_count(weak_count const & r): pi_(r.pi_) #if defined(BOOST_SP_ENABLE_DEBUG_HOOKS) , id_(shared_count_id) #endif { if(pi_ != 0) { pi_->add_ref_lock(); } else { boost::throw_exception(boost::bad_weak_ptr()); } } } // namespace detail } // namespace boost #ifdef __BORLANDC__ # pragma warn .8027 // Functions containing try are not expanded inline # pragma warn .8026 // Functions with excep. spec. are not expanded inline #endif #endif // #ifndef BOOST_DETAIL_SHARED_COUNT_HPP_INCLUDED ------------------------- -- John Torjo -- john@torjo.com Contributing editor, C/C++ Users Journal -- "Win32 GUI Generics" -- generics & GUI do mix, after all -- http://www.torjo.com/win32gui/ -- v1.4 - save_dlg - true binding of your data to UI controls! + easily add validation rules (win32gui/examples/smart_dlg)

John Torjo wrote:
Peter Dimov wrote:
John Torjo wrote:
Hi Peter,
I think there is a bug in your Interlocked* implementation of shared_ptr (http://www.pdimov.com/cpp/shared_count_x86_exp2.hpp).
In atomic_read you have: inline long atomic_read(long volatile const & value) { return value; }
I don't believe this is thread-safe.
(repost)
On x86/IA32, I believe that it's as thread safe as you can get. Note that all count updates go through Interlocked* calls. On IA32, plain reads can only return a somewhat "surprising" value when interleaved with plain writes, and even then, the element of surprise is limited. ;-) I kind of wonder.
Here's my test scenario, which I think is quite effective ;)
The main thread creates a window. Then it internally has a shared pointer to it. Other threads might want access to this window. Each other thread keeps a weak_pointer to it.
Whenever a thread (other than main) wants to access this window, it will query the weak_pointer. The weak_pointer needs to know the LATEST reference count in order to know if the weak pointer is still valid. Thus, atomic_read that simply returns the value is not enough.
atomic_read is only called by use_count. You shouldn't be using use_count to check whether a weak_ptr is still valid, because this can never be thread safe, regardless of the implementation of atomic_read. Use weak_ptr::lock.

Peter Dimov wrote:
John Torjo wrote:
Peter Dimov wrote:
John Torjo wrote:
Hi Peter,
I think there is a bug in your Interlocked* implementation of shared_ptr (http://www.pdimov.com/cpp/shared_count_x86_exp2.hpp).
In atomic_read you have: inline long atomic_read(long volatile const & value) { return value; }
I don't believe this is thread-safe.
(repost)
On x86/IA32, I believe that it's as thread safe as you can get. Note that all count updates go through Interlocked* calls. On IA32, plain reads can only return a somewhat "surprising" value when interleaved with plain writes, and even then, the element of surprise is limited. ;-)
I kind of wonder.
Here's my test scenario, which I think is quite effective ;)
The main thread creates a window. Then it internally has a shared pointer to it. Other threads might want access to this window. Each other thread keeps a weak_pointer to it.
Whenever a thread (other than main) wants to access this window, it will query the weak_pointer. The weak_pointer needs to know the LATEST reference count in order to know if the weak pointer is still valid. Thus, atomic_read that simply returns the value is not enough.
atomic_read is only called by use_count. You shouldn't be using use_count to check whether a weak_ptr is still valid, because this can never be thread safe, regardless of the implementation of atomic_read. Use weak_ptr::lock.
Lets take a look at weak_ptr::lock(): wnd_shared_ptr<T> lock() const // never throws { #if defined(BOOST_HAS_THREADS) // optimization: avoid throw overhead if(expired()) { return wnd_shared_ptr<element_type>(); } try { return wnd_shared_ptr<element_type>(*this); } catch(bad_wnd_weak_ptr const &) { // Q: how can we get here? // A: another thread may have invalidated r after the use_count test above. return wnd_shared_ptr<element_type>(); } #else // optimization: avoid try/catch overhead when single threaded return expired()? wnd_shared_ptr<element_type>(): wnd_shared_ptr<element_type>(*this); #endif } And lets look at expired() bool expired() const // never throws { return pn.use_count() == 0; } Now, see what I mean? Best, John -- John Torjo -- john@torjo.com Contributing editor, C/C++ Users Journal -- "Win32 GUI Generics" -- generics & GUI do mix, after all -- http://www.torjo.com/win32gui/ -- v1.4 - save_dlg - true binding of your data to UI controls! + easily add validation rules (win32gui/examples/smart_dlg)
participants (4)
-
Alexander Terekhov
-
John Torjo
-
Peter Dimov
-
Tomas Puverle