RE: [Boost-users] Re: Condition variable and const-correctness
Mark, (and group) I may not have explained myself clearly in my original message. Your example ThreadedQueue::bMessageAvaliable() const actually demonstrates _exactly_ why I feel that condition::wait should be const. Your method modifies something internally (a boost::mutex; I have no argument whatsoever with the view that locking a mutex changes its state). Then it does some other things that don't affect its const-ness. Then it undoes the modifications it originally made. (By unlocking the mutex) A caller of this function should not care that these things go on internally. In this sense, it is logically const. I would also add that the caller of the function does not care that the precise behavior (say, when it returns, or in other examples, the exact value that it does) of the function can depend on the actions of other threads; this is the point that I had expected resistance on.) Similarly, the condition::wait will modify a mutex (unlocking it, and yes, through a non-const Lock object). It will do some other stuff. Bookkeeping or whatever, maybe even blocking itself. No matter what it does internally, by the time it returns control to its caller, all of this has been undone. The thread is not blocked. It is not in a waiting queue. The mutex that we pointed it at has been locked again. From the caller's perspective, the only things different after the call are: 1. Time has passed. 2. Some other threads have done some things that they might not have done otherwise. What they do is their own business. I don't see how that can affect this function's const-ness. (Am I missing something _here_?) My conclusion is that condition::wait is logically const. I will certainly admit that const-ness may be in the eye of the beholder. I expect that the physically const vs. logically const is a problem with some people. However, there do seem to be other examples of functions that are const (in their signatures) in the same spirit that my intuition tells me condition::wait is. Does this elucidate my position? Weston Markham Mark Sizer wrote: The condition wait() requires a lock, which requires a mutex. The mutex state is toggled from acquired to free to acquired whilst the wait() is waiting. It's the same problem that having a lock creates on a method such as this one: ThreadedQueue::bMessageAvailable() const { { boost::mutex::scoped_lock( _mutexQueue ) return !_queue.empty(); } } The only way to make that work is to declare the _mutexQueue as "mutable". In the case above, I agree with you: In terms of logical "const-ness", these methods are (or can be) const. In terms of physical "const-ness", they are not. Solve the problem with "mutable". However, the condition case is a bit more subtle. ThreadedQueue::messageNext() const { { boost::mutex::scoped_lock lockQueue( _mutexQueue ); if ( _queueOut.empty() ) { // (the lock is released before blocking, // then re-locked before continuing) _conditionMessageReady.wait( lockQueue ); } // get a message } } Is that really const? if the _mutexQueue and _conditionMessageReady are made "mutable" we can MAKE the method const (as shown), but is it? I'd say there is a EXPLICIT state change in the object: Into the waiting state. This is a change, therefore non-const. In the bMessageAvailable() case, there is no explicit state change, you're returning the existing state of the object. There is an implicit state change required to safely do so, which we've decided to hide. The only way this can block forever is bad coding - deadlock. When using a condition.wait(), you are making 2 big state changes: Both the object and the thread involved go from a "running" state into a "blocked" state, perhaps forever if nothing notifies (which is reasonable if the event never occurrs, not a coding deadlock error). That doesn't seem "const" to me. - Mark
Weston Markham wrote:
Mark, (and group)
I may not have explained myself clearly in my original message. Your
I think you did. I just think we disagree on this one, reasonably.
A caller of this function should not care that these things go on internally. In this sense, it is logically const. I would also add that the caller of the function does not care that the precise behavior (say, when it returns, or in other examples, the exact value that it does) of the function can depend on the actions of other threads; this is the point that I had expected resistance on.)
Similarly, the condition::wait will modify a mutex (unlocking it, and yes, through a non-const Lock object). It will do some other stuff. Bookkeeping or whatever, maybe even blocking itself. No matter what it does internally, by the time it returns control to its caller, all of this has been undone. The thread is not blocked. It is not in a waiting queue. The mutex that we pointed it at has been locked again. From the caller's perspective, the only things different after the call are:
1. Time has passed. 2. Some other threads have done some things that they might not have done otherwise. What they do is their own business. I don't see how that can affect this function's const-ness. (Am I missing something _here_?)
My conclusion is that condition::wait is logically const. I will certainly admit that const-ness may be in the eye of the beholder. I expect that the physically const vs. logically const is a problem with some people. However, there do seem to be other examples of functions that are const (in their signatures) in the same spirit that my intuition tells me condition::wait is.
[snipped to end] I say that the difference between physical const and logical const is EXACTLY what you are saying it is: A logically const method does not change the internal state of the object. A phyically const method does not modify any members of the object. A phyically const method is necessarily logically const. The converse may not be true. The distinction is that a logically, but not physically, const method requires "mutable" member variables to compile. The question is: Of what does an object's state consist? We both seem to agree that merely grabbing a mutex (or blocking on an attempt to grab a mutex) is not changing the object's state. Somehow, and this is a gut feeling, the condition.wait() seems different to me. Perhaps because it requires a change in the objects state in order to continue. Let's look at this from the other side: Would the condition.notify() method be considered logically const? I can see some circumstances in which the argument could be made. However, I would say the general case is "no, it is not". Simply because if there is no state change, what's the point of notifying anyone? Given this, a method that waits will block until the object's state is modified. When the method returns, the object's state will be different than when the method was entered (unless it restores the state, in which case I'm fine with logical const-ness). Although the method ITSELF isn't doing the changing, the state of the object changes between the call and the return. That says "non-const" to me. I'm willing to be argued out of it, but I probably won't change my code. - Mark
-----Original Message----- From: boost-users-bounces@lists.boost.org [mailto:boost-users- bounces@lists.boost.org] On Behalf Of Mark Sizer Sent: Tuesday, October 21, 2003 10:04 PM To: boost-users@lists.boost.org Subject: [Boost-users] Re: Condition variable and const-correctness
I say that the difference between physical const and logical const is EXACTLY what you are saying it is: A logically const method does not change the internal state of the object. A phyically const method does not modify any members of the object. A phyically const method is necessarily logically const. The converse may not be true. The distinction is that a logically, but not physically, const method requires "mutable" member variables to compile.
Actually a physically const method might not be logically const. Take for example a string class which has a pointer to a buffer which holds the string (plus maybe some other bookkeeping members). The data pointed to is logically part of the object but is not physically part of it. A physically const method may not change the pointer, but is allowed to change the data pointed to by the pointer as this data is not "physically part of the object", but such an operation is not logically const. Richard Damon -- rbrdamon@rcn.com (Home) rdamon@beltronicsinc.com (Work)
Richard Damon wrote:
-----Original Message----- From: boost-users-bounces@lists.boost.org [mailto:boost-users- bounces@lists.boost.org] On Behalf Of Mark Sizer Sent: Tuesday, October 21, 2003 10:04 PM To: boost-users@lists.boost.org Subject: [Boost-users] Re: Condition variable and const-correctness
I say that the difference between physical const and logical const is EXACTLY what you are saying it is: A logically const method does not change the internal state of the object. A phyically const method does not modify any members of the object. A phyically const method is necessarily logically const. The converse may not be true. The distinction is that a logically, but not physically, const method requires "mutable" member variables to compile.
Actually a physically const method might not be logically const. Take for example a string class which has a pointer to a buffer which holds the string (plus maybe some other bookkeeping members). The data pointed to is logically part of the object but is not physically part of it. A physically const method may not change the pointer, but is allowed to change the data pointed to by the pointer as this data is not "physically part of the object", but such an operation is not logically const.
Ooops. You are correct. I consider this a language bug. Take the following: class A { public: const int& rkiValue() const { return _iValue; } void Value( const int& rkiNew ) { _iValue = rkiNew; } private: int _iValue; }; class B { public: B(A* paForMe) : _paMine(paForMe) { ; } const A& rkaMine() const { return *_paMine; } A& raMine() { return *_paMine; } // this is const-correct const int& rkiAValue1() const { return rkaMine().rkiValue(); } // this will not compile const int& rkiAValue2() const { return raMine().rkiValue(); } // this will compile, even though it's IDENTICAL to the above const int& rkiAValue3() const { return _paMine->rkiValue(); } // this will not compile void SetValue1( const int& rkiNew ) const { raMine().Value(rkiNew); } // this will compile, even thought it's identical to the above void SetValue2( const int& rkiNew ) const { _paMine->Value(rkiNew); } private: A* _paMine; }; In order to get any reliable help from the compiler with const-correctness, one must ALWAY use accessors. Otherwise that pointer case just slips through. Of course, code reviews can catch it.
-----Original Message----- From: boost-users-bounces@lists.boost.org [mailto:boost-users- bounces@lists.boost.org] On Behalf Of Mark Sizer Sent: Wednesday, October 22, 2003 12:45 PM To: boost-users@lists.boost.org Subject: [Boost-users] Re: [OT a bit] Condition variable and const- correctness
Richard Damon wrote: Ooops. You are correct. I consider this a language bug. Take the following:
The issue is that the compiler will only enforce "Physical" constness as this is all it knows. The compiler can not know if the object pointed to is part of the objects logical state or not. This is also why we have mutable, so we can let the compiler know that some parts can be changed without effecting the "logical value" of the object. Your sample is a little off. You call the following to code sequences identical: _paMine->rkiValue() and raMine().rkiValue() there is a significant difference, the first just accesses the pointer (which is const because you are in a const member function), the second takes the const pointer and tries to put it through a non-const reference before calling the const member function. You need to either use rkaMine() to tell the compiler you don't want the non-const ref, or rename rkaMine() to raMine() so you let the compilers overload resolution mechanisms determine by context if you can get the non-const reference. Richard Damon -- rbrdamon@rcn.com (Home) rdamon@beltronicsinc.com (Work)
We both seem to agree that merely grabbing a mutex (or blocking on an attempt to grab a mutex) is not changing the object's state.
You're saying acquring a mutex doesn't change its state?
Somehow, and this is a gut feeling, the condition.wait() seems different to me. Perhaps because it requires a change in the objects state in order to continue.
In this case, I think the names of the operations are misleading as far as constness is concerned. "lock" sounds a lot like something that changes state, but "wait" doesn't--it sounds quite passive by comparison. Scott McCaskill
Scott McCaskill wrote:
We both seem to agree that merely grabbing a mutex (or blocking on an attempt to grab a mutex) is not changing the object's state.
You're saying acquring a mutex doesn't change its state?
It definitely changes the mutex's state. I don't think it _necessarily_ changes the state of the object that contains the mutex. A thread-safe "is the container empty?" method needs to acquire the mutex in order to check the container's empty-ness state, but it does not affect the container, itself. In a class that simply wraps a container to provide thread-safety, I think it can reasonably be argued that the state of the wrapper object is not changed. Obviously the "insert an object into the container" method requires the mutex lock AND changes the state of the container, itself. That method, IMHO, cannot be justifiably made const.
Somehow, and this is a gut feeling, the condition.wait() seems different to me. Perhaps because it requires a change in the objects state in order to continue.
In this case, I think the names of the operations are misleading as far as constness is concerned. "lock" sounds a lot like something that changes state, but "wait" doesn't--it sounds quite passive by comparison.
I agree - it sounds strange. Perhaps I'm simply wrong here. I'll need a better argument than naming conventions, which are notoriously arbitrary, to convince me though :) - Mark
participants (4)
-
Mark Sizer
-
Richard Damon
-
Scott McCaskill
-
Weston Markham