Hacker News new | ask | show | jobs
by obiterdictum 4766 days ago
C++98 "const" code still works exactly the same in single-threaded environment, so this change is not breaking, because no existing code is broken.
1 comments

100% correct; before this standard, there was no standard for how C++ code should behave when run by multiple threads.

Also, I do not think the claim that const member functions cannot have side effects is correct.

For example, it is OK, but very bad practice to have a const member function update a global integer counter.

There are two different considerations:

* what the compiler will enforce: this has nothing to do with thread safety, and will allow what you mention.

* what guarantees the standard library will give:

[17.6.5.9/3] A C++ standard library function shall not directly or indirectly modify objects (1.10) accessible by threads other than the current thread unless the objects are accessed directly or indirectly via the function’s non-const arguments, including this.

This means that the standard library assumes that your const objects will be thread safe in order to guarantee thread-safety itself. Updating a global without synchronization is not thread-safe.

indeed, this article sounds overly alarming. It is true though that it's possible that future code written with the c++11 spec in mind will incorrectly assume some properties of a library which was written as c++98.

In this case the user of the library should be aware of which assumptions the library guarantees, not only what the current compiler does

Also, you can declare member variables as mutable which allows you to modify it from a const function. An example legitimate application for this is acquiring a read lock on a mutex from a const function.
C++11 is finally saying that this is not ok, as it really breaks the const contract, and can produce varying behavior in a threaded situation.

You could also argue that read/write locks aren't legitimate applications (starvation, increased contention, alternate solutions).

But the const function could still accept a non-const reference to a mutex, and still produce varying behavior in a threaded situation. In other words, what you're describing as the const contract sounds a lot like a pure function, which it definitely is not.
I'm not sure how it would produce varying behavior if it satisfies the const contract, in regards to the object itself.

You're passing in a non-const reference, so wherever that came from would be expected to change. However the object itself would not change. Your object would be at StateA regardless of who gets the mutex first.

Also if a function doesn't have _ANY_ side effect how can it be thread unsafe ?
It could rely on another variable for computation, and that other variable could be accessed outside of the function. Eg)

class c { int m1; public: void set_m1( int i ) { m1 = i; } int calc( int i ) const { return m1 * 10; } };

calc is properly const, but it's not threadsafe.

calc is bitwise const function and therefore is perfectly threadsafe: http://herbsutter.com/2013/05/24/gotw-6a-const-correctness-p...

Threadsafety(in this context) means that any sequence of calls of const-functions from any number of threads will have the same result. In your case there is a single const function, and you may call calc(0) from several threads and it always will return the same value. So it is pretty threadsafe.

Replace int with long long on a 32-bit system, and the stance remains the same. But you are right, as written it is thread safe in the given context. :-)
Doesn't matter: any sequence of calls for calc() from any number of threads will always yield the same result.

Mutating m1 non-atomically is completely unrelated here, because non-const functions may be not threadsafe. You should just ensure that const functions are threadsafe.

The only time I could see that not being threadsafe is with something like a double on a system without native support, such that bits could change during operation. Even then I think the information gets pulled out.

On most systems, the value of calc will still be a race condition dependent on what thread gets there first, even if you lock around m1 or make it atomic. Calc will pull the value from m1 into a register, perform the operation, then return the newly calculated value. Another thread changing m1 will not matter.