37 comments

[ 3.3 ms ] story [ 93.1 ms ] thread
Completely unrelated to the content, but it annoys me when sites put 'design' in front of readability. Darkening the background, lightening the text color and reducing the font size is a sure way to make it hard to see.
I dont see that as being a breaking change. In both cases its wrong to mutate the object in the const member, Its just in C++98 no one _explicitly_ told you that. Maybe we should spell other good programming practices out for you so you can start following them before they are stated by the standards commitee

1. Don't use global state 2. Don't use goto 3. Don't, for the love of all things good in this world, use exceptions to do control flow 4. Don't just pass strings around in your functions and deserialise them in your function 5. Make your data members private with public accessors and mutators.

> In both cases its wrong to mutate the object in the const member, Its just in C++98 no one _explicitly_ told you that.

How so? You cannot mutate a data member(without placing mutable before it) in const member function. It is a compiler error. Pretty explicit IMO.

The point of the article is that you should ensure thread-safety when you mutate mutable(sounds funny, I know) members in const methods.

> The most common use of mutable data, and I would argue that the only reasonable use of mutable data is for caching.

This was confusing to read in the context of C++, because I typically associate that view with functional programming. Don't the majority of C++ classes require mutable data to work properly, even the ones in the STL? How do you write std::vector<> without mutable data?

Ok, so I looked a bit more, and it turns out there's a "mutable" keyword...

I've updated the text to emphasize that. Thanks for pointing it out
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.
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.

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.

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.

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.

I think he's trying to get at what happens if an operation becomes non-atomic. In the case of the long long, there could be two register loads, rather than one. Normally, you would expect calc(1) to return m1 at t0, or m1 at t1, dependent on when the change thread hits. However, if your operation requires two register loads (long long on 32), you would have a third value produced, which is neither m1 at t0 or m1 at t1.

This could be considered a non-thread safe operation.

I understand the problem with non-atomicity, but it has nothing to do with thread-safety of calc().

Executing non-threadsafe function, and threadsafe function simultaneously is not a threadsafe operation. But the article says nothing about such situations, it places no restrictions on such situations. The thread-safe requirenment is for const-functions.

I am probably not a good explainer, so here is the link: http://stackoverflow.com/questions/14127379/does-const-mean-...

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.

(comment deleted)
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

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.

I've always suspected not using const all around my OO code is going to save me some trouble sometime. Looks I was right ;)
No.

If you used const correctly, you couldn't change anything about the class, therefore it was also thread-safe. If your class had mutable members or got around the const guarantee through backdoors (e.g. pointer passed in that modified another object, or member pointers that called non-const methods), it wasn't correctly const and shouldn't have been marked as such.

There is nothing wrong with the const keyword, just with programmers who marked things const that broke the const contract. There is no language ambiguity on this.

The language doesn't define "correct const usage" the way you see it. It only specifies what is defined and undefined behavior. Casting a pointer-to-const to a pointer-to-non-const and modifying an object via that is defined behavior as long as the original object is not declared const. (see my comment above)
And that's where functional programming languages are gaining over imperative/oo languages for multi-threaded behavior. Because you have the ability to break the contract, it produces code that is harder to follow, harder to learn for beginners, and harder to debug in parallel/concurrent contexts.

It's a shame that this is more best practice. Hopefully the specification will address this in the future that you can only call const from const, and cast from const to non-const can't be used.

Completely agree. Despite what gurus say, there is hardly an advantage in writing const correct code, other than complying with existing libraries. Const is a nice concept in theory, but it doesn't provide any guarantees, since it is so easy to cast const away from any pointer. As a result, compilers can't do anything in practice with const, other than give an endless stream of compilation errors.
I've actually just looked at the standard and there is a clear example something like this:

  int x;
  int const *cpx = &x;
  int *px = const_cast<int *>(cpx);
  *px = 1; // defined behavior, modifying non-const object through non-const pointer
While this may seem ok here, consider that the same applies to functions taking pointers to const. While this usually means the function won't modify the pointed-to objects, there is no guarantee from the language about that.
const_cast = code smell mutable = code smell

Pretty much anything that breaks the contract is readily apparent when reading/searching the code, and indicates that something needs to be rewritten.

Just because you can break the contract doesn't mean you should break the contract. Const correctness produces more readable/easily understood code.

Don't be lazy, and make life better for anyone that has to maintain and use the code after you.

Often, the point of stuff like 'const' is exactly to give a stream of compilation errors, because they presumably mean the programmer is doing something wrong (either using the const keyword wrong, or using the things marked const incorrectly)

Even if things like 'const' are useless to the compiler in optimisation terms, they provide (weak) contracts for the humans writing the code, and obvious flags when these contracts are broken, by having to be explicit about, in this instance, const_cast's. ('git grep const_cast' => places to examine closely for bugs.)

The problem is that const is also flawed as a contract. Whenever you're using const, you're trying to design a class that behaves as a mutable or as an immutable object, depending on how the pointer/reference is declared. A class that behaves in two different ways depending on the situation is a recipe for disaster, and that's exactly what const asks us to do.

I only noticed this problem when I learned how languages such as Smalltalk/objective-c deal with the same issue: they create different classes for each situation, so if you want a mutable array you have to say so.

I always wrote const member functions assuming they had to be threadsafe. I guess that's the "advantage" of always having worked with multi-threaded code.
The author is a total idiot.

The C++ language is totally unsafe, so when you call third-party code, literally anything can happen.

Just because there might be a convention for "const" methods to be thread-safe doesn't mean that they are actually implemented this way, regardless of what the language spec says.

It's alarming that this guy apparently writes books, hopefully no one reads them.

The author is a total idiot.

The C++ language is totally unsafe, so when you call third-party code, literally anything can happen.

Just because there might be a convention for "const" methods to be thread-safe doesn't mean that they are actually implemented this way, regardless of what the language spec says.

It's alarming that this guy apparently writes books, hopefully no one reads them.