
Why doesn't Boost have any smart pointers that automatically lock a class when a member is used? The code isn't that hard. #include <iostream> struct Dummy { int a; } class Lock { private: Dummy &_d; public: Lock( Dummy &d ) : _d(d) { std::cout << "Locked." << std::endl; } ~Lock( void ) { std::cout << "Unlocked." << std::endl; } Dummy *operator->( void ) { return &_d; } }; class Ref { private: Dummy &_d; public: Ref( Dummy &d ) : _d(d) { } Lock operator->(void) { return Lock(_d); } }; int main( void ) { Dummy d; Ref x(d); x->a = 1; std::cout << x->a << std::endl; } I get: Locked Unlocked Locked 1 Unlocked on my system (g++ 3.3.3 and g++ 3.4.0). Has there been any reason not to do this? -- ------------------------------------------- Dan Day http://razzerblog.blogspot.com

Raz wrote:
Why doesn't Boost have any smart pointers that automatically lock a class when a member is used? The code isn't that hard.
#include <iostream>
struct Dummy { int a; }
class Lock { private: Dummy &_d; public: Lock( Dummy &d ) : _d(d) { std::cout << "Locked." << std::endl; } ~Lock( void ) { std::cout << "Unlocked." << std::endl; }
Dummy *operator->( void ) { return &_d; } };
class Ref { private: Dummy &_d; public: Ref( Dummy &d ) : _d(d) { }
Lock operator->(void) { return Lock(_d); } };
Your code is non-portable. You are assuming that the temporary Lock(_d) will be constructed directly in the return value, but this is not guaranteed. That aside, I don't know why Boost doesn't include a locking pointer. Probably because locking at pointer level is rarely the best solution when access to a class needs to be synchronized.

"Peter Dimov" <pdimov@mmltd.net> wrote:
struct Dummy { int a; }
class Lock { private: Dummy &_d; public: Lock( Dummy &d ) : _d(d) { std::cout << "Locked." << std::endl; } ~Lock( void ) { std::cout << "Unlocked." << std::endl; }
Dummy *operator->( void ) { return &_d; } };
class Ref { private: Dummy &_d; public: Ref( Dummy &d ) : _d(d) { }
Lock operator->(void) { return Lock(_d); } };
Your code is non-portable. You are assuming that the temporary Lock(_d) will be constructed directly in the return value, but this is not guaranteed.
I'm not sure I'm following "constructed directly in the return value"; but, it will be constructed before the call to Lock's Dummy *operator->( void ) as implied by x->a, below. Isn't this all that is required?
int main( void ) { Dummy d; Ref x(d);
x->a = 1; std::cout << x->a << std::endl; }

Mark Deric wrote:
"Peter Dimov" <pdimov@mmltd.net> wrote:
struct Dummy { int a; }
class Lock { private: Dummy &_d; public: Lock( Dummy &d ) : _d(d) { std::cout << "Locked." << std::endl; } ~Lock( void ) { std::cout << "Unlocked." << std::endl; }
Dummy *operator->( void ) { return &_d; } };
class Ref { private: Dummy &_d; public: Ref( Dummy &d ) : _d(d) { }
Lock operator->(void) { return Lock(_d); } };
Your code is non-portable. You are assuming that the temporary Lock(_d) will be constructed directly in the return value, but this is not guaranteed.
I'm not sure I'm following "constructed directly in the return value"; but, it will be constructed before the call to Lock's Dummy *operator->( void ) as implied by x->a, below. Isn't this all that is required?
No, because ~Lock will be called twice.

On Tue, 15 Feb 2005 03:04:35 +0200 "Peter Dimov" <pdimov@mmltd.net> wrote:
struct Dummy { int a; }
class Lock { private: Dummy &_d; public: Lock( Dummy &d ) : _d(d) { std::cout << "Locked." << std::endl; } ~Lock( void ) { std::cout << "Unlocked." << std::endl; }
Dummy *operator->( void ) { return &_d; } };
class Ref { private: Dummy &_d; public: Ref( Dummy &d ) : _d(d) { }
Lock operator->(void) { return Lock(_d); } };
Your code is non-portable. You are assuming that the temporary Lock(_d) will be constructed directly in the return value, but this is not guaranteed.
I'm not sure I'm following "constructed directly in the return value"; but, it will be constructed before the call to Lock's Dummy *operator->( void ) as implied by x->a, below. Isn't this all that is required?
No, because ~Lock will be called twice.
Fair enough. I guess I was willing to overlook the absence of a copy ctor in Raz's pseudo-code that "does the right thing". The thing that I took away from the idea was the forced creation of a temporary that is scoped to the remaining duration of evaluation of the "full-expression" as presented in 12.2 of the standard. Yes, I did need to look it up :) ; as I was operating under the bogus belief that destruction of the temporary was a { ... } scoped thing and may be implementation dependent. Further, I'm used to following the { scoped_lock lk(mtx); ... } formula; making Raz's idea intriguing, at least. I guess the second paragraph of your initial reply gets at the point; is locking to "full-expression" scope useful? I can think of a danger: cout << x->a << long_running_overloaded_insertion_rhs;
From my read, the lock wouldn't be given up until after the second << gets evaluated. Does this make sense; or am I missing something else?

Mark Deric wrote:
On Tue, 15 Feb 2005 03:04:35 +0200 "Peter Dimov" <pdimov@mmltd.net> wrote:
No, because ~Lock will be called twice.
Fair enough. I guess I was willing to overlook the absence of a copy ctor in Raz's pseudo-code that "does the right thing".
It's not easy to add a copy constructor that does the right thing, though. :-) It needs to be a "move constructor" modeled after auto_ptr, or it needs a reference count. Of course you can always abuse shared_ptr<Dummy>( &d, mem_fn(&Dummy::unlock) ) as a Lock class. ;-) More evil ideas at: http://boost.org/libs/smart_ptr/sp_techniques.html#as_lock http://boost.org/libs/smart_ptr/sp_techniques.html#wrapper [...]
I guess the second paragraph of your initial reply gets at the point; is locking to "full-expression" scope useful? I can think of a danger:
cout << x->a << long_running_overloaded_insertion_rhs;
From my read, the lock wouldn't be given up until after the second << gets evaluated. Does this make sense; or am I missing something else?
Consider also: cout << x->a << x->a; which (a) will be a problem if the mutex is not recursive and (b) locks twice. (a) can be avoided: cout << x->a; cout << x->a; but now the two lines may actually output different values, if x->a is modified by another thread. The idiom may have its uses, but the more traditional approach of locking at logical blocks instead of every access is more efficient, doesn't require recursive locks, and within the logical block the state of the object will be consistent.
participants (3)
-
Mark Deric
-
Peter Dimov
-
Raz