Why the C++ standard ships every three years
herbsutter.com
herbsutter.com
They haven't introduced the features other language have, they've introduced poor rip-offs that fail to work as one might expect having worked in any of the other languages they're derived from.
The problem is it's not different than it used to be, the cracked foundations are exactly the same as they've always been always been but there's a bigger and more complicated house on them. C++ is starting to feel like the Winchester mystery house.
In the past it was thought that people would use safer high level languages and then drop down to C for performance. That vision just doesn’t seem to work out in practice - except at the cross process level.
The trade off to this is language complexity. If you want a simple language C++ isn’t for you - and that’s OK.
Well, about half of the Python ecosystem seems to disagree.
I really don't understand the animosity non-C++ developers have towards the language. You can still use Python, Rust, etc if you want. Nobody wants to force you to use C++.
Until then I work in it because my goal (and my job) is to deliver amazing products and experiences to my customers and if I had to do that in COBOL, I’d do that too. My job is not about the language for me, it’s about what I’m doing with it.
Pre-11 C++ was a bad language. The gains of leaving it to something like Java (that isn't even good by today's standards) were huge. The new standards are making it possible to create better code on the language, but it is still far from a modern language and the added features can not remove complexity, they can just add to it.
There are countless examples of the so-called modern C++ features reducing complexity in code.
For example, it is now considered a code smell to ever write "new" or "delete" in your code. Imagine that, never using these words to manually allocate and delete memory in C++! But it's true, unique_ptr in particular makes so much simpler and safer.
Type inference with auto doesn't just save typing, it improves performance in many cases and reduces a lot of complexity, while also avoiding uninitialized variables.
These are just a few of at least a dozen examples that come to mind about reducing code complexity with more modern C++ features.
auto p = unique_ptr<Foo>(new Foo());
Totally works, and may be easier to remember if you dont like the "magic" parameter forwarding.
e.g. foo(unique_ptr<X>(new X), unique_ptr<Y>(new Y)) is a leak waiting to happen.
https://stackoverflow.com/questions/37514509/advantages-of-u...
They're glaringly inconsistent because they behave differently with regards to ownership, and the fact that you can't copy them or pass them around in certain cases is because they're designed to stop you from doing this as it would undermine the reason you're using the class.
Like I said, if you look at all the logic, there's a good reason why everything is the way it is. The problem is I can't use the things without looking them up. Usability of my language is a big deal for me, which is why I hate unique_ptr.
no it's not, it never has been. The point of modern C++ is to get rid of owning pointers.
C++ programmers still need to know what "new" and "delete" do, so they can work with older code that uses them. They also need to learn what "auto" does, so they can work with newer code that uses it. (The behavior of "auto" isn't trivial; how many C++ programmers understand the difference between "auto v = <expr>;" and "decltype(<expr>) v = <expr>;"?)
...instead you'd use "decltype(auto) v = <expr>", which is equivalent. And people certainly use decltype(auto), or else it wouldn't have been added to the language, 3 years after regular auto.
Plus it usually makes the code cleaner by focusing on structure not types. As any construct, it can of course be abused.
Every big project is almost entirely different. They use different sets of features - some overlap more, some less. Many developers like to use C libraries, because they are easy to wrap in their version of C++. When you shop for libraries you often have to think if their version of C++ will work with yours. There is some consensus around STL and Boost, so at least that is relatively straightforward.
E.g. in the C->C++ transition most malloc's were left alone. If you wanted to add a ctor/dtor then you would go refactor them as necessary. It also encouraged you to encapsulate your work moreso than you would have otherwise.
But it is also true that forcing all the code in a project to use a single dialect is expensive. Developers need to decide what that dialect is --- not just once, but constantly as C++ evolves. ("Is it time to use Ranges yet? Concepts?" etc.) You need to do expensive refactors every time you update the dialect. You need to enforce the dialect at code review time (harder as it changes). Every time you import third-party code into the project you need to update its dialect.
A constantly evolving language that regularly deprecates commonly-used features in favour of "better" alternatives, like C++ does, is problematic. The faster pace of C++ evolution is making this problem worse.
Aside from exceptions (Can't use them safely if RAII is not universal) and shared pointers, most new language features are pretty localized in effect. E.g. using a lambda in a function originally written in C++98 does not make the existing code less safe. Only your sense of aesthetics would force you to update the rest of it.
You may not be able to if the lambda captures state and you need to convert it to a function pointer.
Or did you want them to deprecate large swathes of the previous standard?
As for the rest of your rant... it's mostly just a rant, with very little substance.
This sounds like hyperbole to me. I've worked in other functional languages professionally for many years, but when I write C++ with consistent use of std::function and lambdas, I get many of the same benefits and a very enjoyable workflow. Is it the same as Haskell or Clojure? No, because they are totally different languages. But within the C++ world, they offer a great productivity benefit that I don't think satisifies the definition of "rip off".
std::move doesn't move anything, it casts an object as a movable reference (equivalent to static_cast<T&&>). The compiler doesn't preclude you from accessing the old object (or say anything at all) and there's no guarantee it actually did get moved, it's just a hint. That's not real move semantics, it's a shoddy knock-off.
You also have to manage a new constructor, new reference type and the opt-in because the default remains not using move semantics. Worse yet, there's varying agreement on what you can or should do with the source of a moved value, the STL containers will all continue to let you use the old value, it's just empty now. That's not standard behavior because there is none, and it's de facto now because it's in the STL. What a nightmare. [1]
std::variant is supposed to be associated values on enums, but of course, it doesn't do that either. You don't match, you create a new struct with a bunch of operator() methods on it [or overload a lambda?!] and throw it at std::visit. You can only have one variant of each type. Then it throws exceptions if you start mucking about in ways you shouldn't. There's no context. It's dreadful. [2]
[1] http://yacoder.guru/blog/2015/03/14/cpp-curiosities-std-move...
In any kind of correct code, the difference between a move and a copy is only performance. If a copy were to happen where a move was requested then the code is just as correct, so I find it strange to get so hung up on it not being “real”.
Also, if move is the only available option, and move cant happen, you get a compiler error. If performance is correctness for a type that is expensive to copy, make copy not an option.
Also, there is a move constructor by default so it’s not opt-in, you opt out only if you start screwing around with copy / assignment / destructors which you usually shouldn’t need in modern code anyway.
Sure, the state of moving on the moved from object is unspecified, but really, I can’t think of a time when I’ve written code that would care. It’s kind of a non-problem.
If you really want to reuse an object after a move I question your motives, but you should just reinitilaise it by assigning a freshly constructed value and the result of that is of course standard.
That's just not true when you take smart pointers into account. unique_ptr is pretty obvious, since it can't be copied. But shared_ptr is more devious, as there is a clear semantic difference between giving someone a copy of your shared_ptr vs moving your copy to them. And, given that destructors are often used for more than simple resource cleanup (e.g. they are sometimes used for releasing locks), the difference between a move and a copy can have a huge impact on program behavior.
Whether this matters really depends on the code you're trying to write of course.
It's nothing earth-shattering (that stuff is coming in C++20) but these are all features that improve the language and allow you to write better code. Consider e.g. if constexpr which can eliminate so much SFINAE cruft. Or CTAD to reduce the need for makeXYZ() functions.
Not really... Rust 1.0 is from mid-2015, well past 2011.
C++11 was a really big shakeup. In contrast, C11 isn't a major difference over C99. Sometimes I read about the rapidly evolving modern C++ and I wonder if they are moving too fast, as large chunks of the community have not even caught up with what is already there.
It was possible and common to have pretty "modern" styles in c++03, you'd just have to do without lamdbas etc. and be using less of the 'std' namespace.
The nonsense needed for "variadic" templates in C++03 isn't "just do without lambdas". You basically have to walk on eggshells to get the equivalent of unique_ptr. Not having to type std::vector<SomeTypeName, WhateverBuffer>::const_iterator changes the way you write code, too.
The stronger guarantees on copy elision lets you skip return by reference nonsense more aggressively.
Sure there are a lot of small accumulating changes, but they can easily be caught up on by reading the documentation when you actually need those features.
On the other hand, I've been waiting for modules, concepts and networking literally since 2012 - and I only started using C++ in 2011, with around 2 years of experience in C. I don't think anyone is moving too fast in that direction.
Edit: I still remember the graphic from the committee/Herb, which showed 2014 for the networking TS, and 2017 for concepts/modules. The "networking TS" then ended up being like one .pdf file with functions for LE/BE conversion, and well obviously concepts and modules aren't in C++17. (Neither is any actual networking.) And while concepts look to be on a fairly good path for C++20, I'm going to be very surprised (and happy) if they'd manage to get modules in, too.
I actually think that the lack of a popular cross-platform package manager is much more harmful. It's amazing what GitHub and CocoaPods did to the niche Objective-C community in such a short time, while C++ still isn't a language where people celebrate cool open source libraries.
And you yourself mentioned Boost, which is kind a code ecosystem of its own, all open source. But I think C++ has problems with libraries because it’s such a pain to add a library to C++. If the library is C++, you can’t ship a binary and header file like with a C library because the ABI needs to match, so you have to compile the thing. And then people like Boost do template wizardry that consumes compile time to produce magic, but some developers value the compilation speed over magic, so they don’t use anything heavily templated. But if it isn’t templated, it likely could be written in C if it isn’t a big UI framework, and be available for everyone. So, no C++ library ecosystem.
I think it's a bit of a catch-22. C++ projects are so rare and monolithic that reinventing the wheel every now and then. But maybe more (smaller) projects would happen in C++ if there were polished libraries for reasonably common requirements like sending a SOAP request.
... and when there's essentially a single build system for Swift code. While for C++ there are several.
> C++ projects are so rare and monolithic that reinventing the wheel every now and then...
...does not hurt much.
Also, I see cool C++ open-source projects being released fairly frequently.
In practice there are often complications, however.
Usually, it's just office politics: Those cases can be quite difficult -- you really need buy-in and TRUST from management (and have to do the right political plays, etc.) to be able to cut through the bullshit and "allow" feature slips. Books have been written about this scenario.
Rarely, deadlines are imposed by Real Politics, aka: law... which can make for Interesting Times. This can range from "quite difficult" to "too easy!", so I have no advice here.
What happens when your software is in a broken state that's worse than what you had before?
If you go to shows or conferences, showing or talking about something is the best way to have something, and the deadline focuses you like nothing else.
(that said, sometimes I wonder if I would have something much more polished with a last-minute magical 2-week reprieve)
The same applies for a personal project that's delivered as a gift to a family or friend. I guess I should write sometime about the Raspberry Pi-based Christmas present I gave my father in 2014, even though he stopped using it scarcely a year later. For this thread, the point is that the deadline made this the only personal coding side project that I've completed in several years.
I am also a strong believer that it is a sign of gross lack of internal discipline, or at least severe lack of confidence. It means you still suffer from "unknown unknowns" and therefore can't come up with and stick to a proper estimate (even if it is something broad, like Q2 2019). This doesn't say good things about a software team, in my opinion.
I know very well that estimates are difficult. But, again in my opinion, going with "we will release the next version on February 20th, now let's discuss what features we can implement during that timeframe" as opposed to "here is a list of features we want in the next version, now let's discuss when we can finish them by... actually never mind, we will just tell people 'when it is ready'".
I've heard Accelerated C++ is a good introduction, but it's quite old at this point. Would Accelerated C++ followed by Effective Modern C++ bring someone up to speed with modern C++? Is there a single book or online resource that would service this purpose better?
I used Marc Gregoire's Professional C++ which has a version that was published last year and includes C++17, alongside Scott Meyer's line of books, and watching a variety of YouTube videos (e.g. Jason Turner, CPP talks...)
This SO post is a great guide for where to look: https://stackoverflow.com/questions/388242/the-definitive-c-...
Reading Essential C++ cover-to-cover was very worthwhile in my job as a programmer in AAA games. Books beyond that have mostly involved thumbing around different items to see which I might find useful at some point. Games in particular tend to be very performance sensitive, but also compile-time and debug-performance-sensitive. A lot of the STL is not as respectful of those latter two, meaning a lot of game code uses little or even none of the STL. (Working with UE4 will involve using none, for example.) I'd definitely focus more attention on understanding core language features that the huge amount of content that exists in the STL.
By far the best element of C++ that a lot of other languages lack is const and const-correctness. The second best would be the robust features templates have in comparison to generics of other languages (though the applications allowed by that robustness can be mildly horrifying at times).
Add to that volatile-correctness... which most people aren't even aware of. http://www.drdobbs.com/cpp/volatile-the-multithreaded-progra...
You, who've surely carefully read the article, understood it in its entirety, and played around with the notion to get a feel for its upsides and downsides, very insightfully reduced it all down to "very bad advice" with zero elaboration. You find that compelling?
I would be careful with those "first-order approximations".
There's a reason that it's been proposed for removal (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p115...)
The author is using volatile as though it implied a barrier.
> The author is using volatile as though it implied a barrier.
No, you're just not reading the article. Please go read the article. And I don't mean skim. I mean actually read it with the assumption that you have zero clue what it's going to say, because that's more accurate than your current assumption. Then if you still think you're correct, please explain to exactly which line(s) in the code are broken and how precisely that actually undermines the points the article has been making. You will struggle to do this.
In case it helps, for your reference: the author isn't, and never was, a random C++ dummy.
Simply not true. It has limited use with memory mapped I/O (although even there it misses necessary guarantees), but is not intended to work with threads.
> So all you have to do to make Gadget's Wait/Wakeup combo work is to qualify flag_ appropriately:
class Gadget
{
public:
... as above ...
private:
volatile bool flag_;
};
Is not correct, and will not work reliably.I spent some time working with Andrei at Facebook, and he's a smart guy, but this article is wrong.
Don't do what he says here.
Volatile needs to go away.
https://news.ycombinator.com/newsguidelines.html
Getting personal, bringing up whether someone read an article properly, making uncharitable interpretations of other comments, snarking, and posting in the flamewar style are all things they ask you not to do and which we're trying to avoid here. Not that your comments were anything like as bad as some that we see, but even in seed form these things have ill effects.
Edit: Someone deleted one of their replies here. Just wanted to say thanks, I read it and I think it'll be helpful moving forward.
I’m pretty sure you did not try to follow the guideline in your initial comments. (see [0])
"This isn't what the article says. For instance, in paragraph n, the author states 'x, y and z.'"
I agree I should be careful with "first-order approximations", but honestly I was being gentle because I do love drdobbs. But all of the things it talked about have been replaced with things that aren't broken in subtle and hard to debug way.
Volatile simply cannot be used a general purpose, portable, synchronization primitive.
No, it doesn't say it because it's not trying to make the point that you assume it's trying to make. Honest question: did you fully read and digest the article before commenting? If so, tell me on precisely which line you saw a lack of a memory barrier causing a problem (describe the race condition & bug you found) and explain how exactly you found that to undermine the point of the article.
Yes, it goes on to elaborate on a basically unrelated use of volatile to control access to member functions on classes, which deserves a separate discussion - but you don't even need to get past the first few paragraphs to see that it promulgates the idea that broken makes concurrent access safe. It doesn't.
The stuff the article recommends is straight up UB in modern C++. Volatile has never been specified to work properly with threads, but before C++11 when there was no alternative, some limited use in that context, preferably hidden away from the casual user, may have been acceptable. Recommending these techniques today, however, makes no sense.
The stuff you're taking about is not the same stuff I'm talking about. There's nothing UB about the locking pointer pattern and how it uses volatile. Read the article in full. It has a specific thesis that is just as valid today as it was 20 years ago, and that thesis is NOT the 2001 malpractice you're talking about.
Yes, it's not UB in the race sense, because he is using mutxes everywhere there and just sort of overloading the volatile qualifier to catch member function calls outside the lock. In addition to being UB, it's weird - why not just escapsulate the object itself inside a class that only hands out access under control of a lock? That is, why have the volatile object passed in from the outside if you will never legally access the object?
The very premise of this article, that volatile is for concurrently modified objects across threads is false in modern C++ - and the very first example is a faulty use of volatile under the assumption that unguarded concurrent volatile access is safe.
Can you point me to which part of the standard says that it's UB to cast away a volatile reference to a non-volatile object? See my example in [1] if you don't see why the object itself doesn't need to be volatile.
> it's weird
No, you're just not used to it. It's perfectly fine once you use it a bit. And regardless, there's quite a huge chasm between "it's completely wrong and undefined behavior" and "I don't like it, it's weird".
> why not just escapsulate the object itself inside a class that only hands out access under control of a lock?
That's a separate discussion. Right now we need to get the UB-ness claims out of the way. Once we agree it's correct in the first place then we can discuss whether it looks "weird" or what its use cases might be.
That is not UB, it's only UB if the object was defined volatile, which is what the article does, explicitly:
> You should define objects that are shared between threads as volatile and never use const_cast with them — always use LockingPtr automatic objects. Let's illustrate this with an example [Example goes on to define the object volatile]
> No, you're just not used to it. It's perfectly fine once you use it a bit. And regardless, there's quite a huge chasm between "it's completely wrong and undefined behavior" and "I don't like it, it's weird".
There might be a glimmer of something interesting in overloading the use of volatile on user-defined types as a second type of access control analogous to "const" but that you use for some other purpose, e.g., gating access to functions based on their tread-safety, or anything else really.
This article doesn't make a convincing case for it because the first example is UB, the second example is UB, it propagates the broken notion that volatile is useful for concurrent access to primitive types, it doesn't include any discussion of modern techniques like std::atomic<>, etc. Of course, that's no fault of the author, who wrote it 2001 when the well-defined way of doing things was 10 years away.
It's mostly a problem when people try to promote this, today, as an insightful view on volatile and multithreaded code. As a whole, it isn't and propagates various falsehoods that people have been trying to get rid of forever. What glimmer of an interesting point is in there regarding using volatile-qualified objects as a second level of access control orthogonal to const is washed out by the other problems.
> That's a separate discussion. Right now we need to get the UB-ness claims out of the way.
It's UB. Just admit that it's UB because the flag_ example does concurrent access to an object from different threads, at least one of which is a write, and the LockingPtr and follow-on examples are UB because they involve casting away volatile from a volatile-defined object.
If you can agree with that, then maybe you can present a related technique, different to the one in the article, which uses volatile in a useful way.
Can we step back for a second?
Go back to my top comment. Why did I even post this article in the first place? The point was that "volatile-correctness" is (basically) awesome, and it's hard to get something like it in other languages. This article is where the idea originated from, so I linked to it. i.e.: "There's something called volatile-correctness, which you can learn about by reading this article." The point was not "read this article and blindly sprinkle volatile across your codebase in exactly the same manner and you'll magically get thread safety".
What were you supposed to take away from the article? The idea of volatile-correctness, the idea that you can use a locking pointer to regulate multithreaded access to a class's methods. The idea that volatile acts as a helpful type annotation in this regard, independently of its well-known effects on primitive objects. You can apply it easily without ever marking objects as volatile, like I just showed you in that example. Yet somehow instead of actually extracting the fundamental concepts and ideas from the article, you and everyone else here are trashing it by insisting that the only possible way anyone can read that article is a naive verbatim copy-paste of its text from 2001 to 2019...? Why?
> If you can agree with that, then maybe you can present a related technique, different to the one in the article, which uses volatile in a useful way.
But omitting a couple volatiles doesn't make it a different technique! You just skip the incorrect uses of volatile. The technique is the same..
So yes, let's get the UB claims out of the way - but agreeing that it's UB. Not just the flag_ example, but with the LockingPtr example that is the "point" of the article.
> you and everyone else here are trashing it
To be clear, I'm not really "trashing" the article. It's a relic of its time. I am trashing the idea that it's somehow a good introduction to any clever MT technique today.
> by insisting that the only possible way anyone can read that article is a naive verbatim copy-paste of its text from 2001 to 2019...? Why?
I explained it earlier: because the article has too many flaws to be a clean illustration of the technique. It starts with UB, ends with UB, makes wrong assertions about the purpose of volatile, etc.
Again, I agree there might be a glimmer of something here - but this article isn't the way to show it. The reaction you got was expected and fine. I can imagine a different article, written today, without the claims about the purpose of volatile, without the flag example, without the UB of casting away volatile from volatile objects, acknowledging the existence of std::atomic and how this technique complements or replaces it. That could be useful.
I looked at your example, and yes, I see the potential if you want to have an object with a thread-safe and non-threadsafe interface split like that (or really any split: you can overload volatile like that for any type of access control where you can cleanly divide the functions like that). It has the unfortunate side effect that volatile is not for that, and it implicitly makes all your members volatile and hence may pessimize code generation. I guess it doesn't matter that much if all the volatile functions follow the pattern of immediately shelling out to a non-volatile function though.
I would also not expect it to pessimize code generation, since the final dereference should always be of a non-volatile pointer, though I suppose an optimizer bug might make it behave otherwise.
You can combine it with atomic, they're not substitutes. It could let you implement two versions of an algorithm: a lock-free multithreaded one, along with a single-threaded one that uses relaxed accesses (or even fully non-atomic accesses, had C++ allowed that). And then you'd auto-dispatch on the volatile modifier. The possibilities are really endless; I'm sure the limiting factor here is our imagination.
I've thought about the other types of split for a long time too, and I haven't managed to come up with other compelling use cases, even though I also feel they should exist. It would be interesting if someone could come up with one, because the ability to have commutative tags on a type seems really powerful.
When the article was written, there was no real alternative, and volatile accidentally worked nicely on certain architectures. It failed on others. It absolutely was never designed to do what you’re trying to defend. It’s always been non-portable, implementation and architecture behavior on how it handled memory read/write barriers. Now that there’s proper ways to do barriers portably, the volatile approach is terrible advice.
C++ 11 addressed this all in a proper manner, after much research and many papers on the matter. Since then, for major compilers on major architectures, the new C++11 features have been implemented correctly. Volatile has zero use for correct multi threading code. It only has use for memory mapped hardware from a single properly synchronized thread.
Your article, as people keep telling you but you seem unable to accept it, is wrong. It’s now absolutely not portable, it’s inherently broken, and leads to undefined, hard to debug, terrible behavior for threading issues.
Go dig up the backstory on how C++11 got its threading model and dig up the More Effective C++ chapter on it to learn why your article is bad.
The idea is this you can use volatile like below. It's pretty self-explanatory. Now can you look through this code and tell me where you see such a horrifying lack of memory barriers and undefined behavior? (And don't point out something irrelevant like how I didn't delete the copy constructor.)
#include <mutex>
template<class T>
class lock_ptr
{
T *p;
public:
~lock_ptr() { this->p->m.unlock(); }
lock_ptr(volatile T *p) : p(const_cast<T *>(p)) { this->p->m.lock(); }
T *operator->() const { return p; }
};
class MyClass
{
int x;
public:
MyClass() : x() { }
mutable std::mutex m;
void bar() { ++x; }
void foo() volatile { return lock_ptr<MyClass>(this)->bar(); }
};
void worker(volatile MyClass *p) // called in multiple threads
{
p->foo(); // thread-safe, and compiles fine
p->bar(); // thread-unsafe, and compile-time error
}
#include <future>
int main()
{
MyClass c;
auto a = std::async(worker, &c);
auto b = std::async(worker, &c);
a.wait();
b.wait();
return c.x;
}Yes I do. It’s simply wrong. What it says about type annotation is correct, but has zero to do with threading because volatile has zero meaning for accesses from different threads. It then uses volatile to (incorrectly) build threading code. You seem to think volatile has some usefulness for threaded code; it does not. You think volatile adds benefit to your code above; it does not. The type annotation does not give you the ability to have compilers check race conditions for you - it works on some and will fail on others.
Add volatile to your bar function. Oops, got race conditions. Volatile is not protecting your code; properly using mutexes is. Requiring programmers to intersperse volatile as some type annotation makes code more error prone, not less. One still has to correctly do the hard parts, but now with added confusion, verbosity, and treading on undefined behavior.
I think you believe his claim “We can make the compiler check race conditions for us.” because you’re relying on the same claim compilers will check volatile in the manner your code above does. That’s undefined behavior, open to compiler whims. Good luck with that. There’s a reason C++ added the more nuanced ordering specifications - to handle the myriad ways some architectures worked (and to mirror discoveries made in academic literature on the topic that happened after this article was written).
This article is even mentioned in the proposal to remove volatile from C++ altogether http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p115.... I’ve known about this for some time, and hacking in type annotations like this adds no value; it simply makes a mess.
More errors from the article, which is why people should stop citing it:
First sentence:
“The volatile keyword was devised to prevent compiler optimizations that might render code incorrect in the presence of certain asynchronous events.”
This is simply wrong. The article goes on to try to make multithreaded code correct using volatile.
More quotes from the article that are simply wrong: “Although both C and C++ Standards are conspicuously silent when it comes to threads, they do make a little concession to multithreading, in the form of the volatile keyword.” Wrong; see Sutter quote below. “Just like its better-known counterpart const, volatile is a type modifier. It's intended to be used in conjunction with variables that are accessed and modified in different threads.” Wrong. See Sutter quote, and ISO standards. Volatile was never intended for this, so was never safe for doing this. “In spite of its simplicity, LockingPtr is a very useful aid in writing correct multithreaded code. You should define objects that are shared between threads as volatile” wrong on so many levels. The referenced code will break on many, many architectures. There is simply no defense to this.
The article has dozens more incorrect statements and code samples trying to make threadsafe code via volatile.
I’ve written articles on this. I’ve taught professional programmers this. I’ve designed high performance C++ multithreaded code for quite a while. It’s simply wrong, full stop.
Here’s a correct destruction of the Dobbs article by someone who gets it [1]. They, like you, were once misled by this article.
The money quote, from Herb Sutter “Please remember this: Standard ISO C/C++ volatile is useless for multithreaded programming. No argument otherwise holds water; at best the code may appear to work on some compilers/platforms”
I suspect you’ll still stick to the claim this article has value, given your insistence so far against so many people giving you correct advice. Good luck.
[1] https://sites.google.com/site/kjellhedstrom2/stay-away-from-...
Hardware interrupts and UNIX signals are the asynchronous events in question, and C's volatile is still useful in those contexts, where there is only a single thread of execution.
Here’s a compiler showing that your use fails on some systems:
The problem with std::atomic<T> is that it may be implemented with a mutex, in which case it can deadlock in a signal handler. But as you say, you can check for that with is_lock_free.
Oh, and sig_atomic_t is not guaranteed thread-safe, only signal safe. The difference is when you move your code from a single cpu to dual cpu system it breaks. I ran across this some time ago moving stuff to an ESP32.
Atomic so far works best across the chips I’m poking at.
Templates are nowhere near capabilities and ease of use of languages with proper (read: not accidental) macro/metaprogramming systems (e.g. Lisps) or languages with modern generic type systems designed from the ground up (Haskell, Scala/Dotty, Idris, etc). Templates are IMHO a powerful hack, but hack is still a hack with all the consequences - terrible error messages, slow compile times, difficult debugging, late error detection, a lot of accidental complexity caused by templates not being first-class citizens etc.
C++-style const appears to be stronger than just immutability, since you can have immutable objects in C++, but you can also pass const references to mutable objects.
Ruby is hardly expressive at all in this respect - you can express to the interpreter very little about types, and the interpreter won't help you much at all.
> you can express to the interpreter very little about types
Dynamic types are still types. Only the error detection moment is different, but lack of static types doesn't mean low expressivity.
Also, typing is not the end of all the things. Most languages I listed have much stronger metaprogramming capabilities than C++. Scala, Rust, Template Haskell macro systems are superior to C++ templates.
You can have immutable objects in C++, but C++ offers almost nothing to make dealing with such objects fast and easy. Also the lack of GC makes designing persistent data structures an order of magnitude harder task than in most other languages.
As for shared_ptr, they are a good idea when you don't care about performance. And they don't solve cycles, which may appear in some structures (e.g. graphs).
Technically C++ const can be used to implement immutable types just as they exist in other languages (and can be hidden behind a library entry point) but I agree that conceptually it's easier to think of an immutable string or vector as an inherent property of the object rather than one applied. And in C++ I don't think you can prevent casting away constness.
> Templates are nowhere near capabilities and ease of use of languages with proper (read: not accidental) macro/metaprogramming systems (e.g. Lisps) or languages with modern generic type systems designed from the ground up (Haskell, Scala/Dotty, Idris, etc). Templates are IMHO a powerful hack, but hack is still a hack with all the consequences - terrible error messages, slow compile times, difficult debugging, late error detection, a lot of accidental complexity caused by templates not being first-class citizens etc.
Templates were not an accidental hack for macros; as Stroustrup once said to me, "the ecological niche of 'macro' had already been polluted so templates were my only way to put macros into the language." I agree it sucks next to lisp macrology (but as a lisp developer since the 1970s I would say that wouldn't I?) but hell, the language makes a distinction between expressions and statements so there's only so much you can do.
Well, UB prevents you from doing it if the object is originally const.
My bigger gripe with the C++ (and C) const system is the lack of transitivity. A function taking a const X& may still modify e.g. the contents of an exposed pointer member of X.
Also have a look at Mike Acton's DOD videos -- he'll tell you that modern c++ (and even oop) is garbage, and he'll be right for his own particular case :)
I know less about the exact details, but I also think that having something to play with helps; I believe that this is what’s going on with with the move towards more TSes before shipping spec text, right? Being able to try things out in nightly helps us a lot.
I think this phrasing contradicts your point. Do you mean "more frequently", or am I missing something?
You seem to be thinking that the compiler devs wait until a release is done to start implementing a release, which is not what happens. Part of the big benefit of releasing every three years is that it makes continuous development actually possible: compiler devs have a better idea of how stable things are, and so which things aren’t going to need to be completely reimplement in another 3 years.
I do not understand where you get a “catch their breath” mentality - none of the modern c++ compilers have a three year cadence, most run with approximately annual major releases.
And it's not like they've been only adding small features every 3 years. If they were then my position would be different too. But small features being small, people can live without them. Not having them is a lot better than not being able to use the language at all.
You didn't actually say what those problems were, you didn't provide any evidence that any bugs you hit were as a result of an increased standardization rate.
You're also assuming that somehow spending more time with unstable language features will result in fewer problems, which is something where we know you're wrong. Because we've seen what happens when a longer cycle exists, we know your claim that the compilers will be less buggy is exactly wrong.
You misunderstood the argument. The claim was not that the VS problems were due to the increased standardization rate. They weren't C++-related at all. Rather, the problem was that I couldn't move onto 2017 due to unrelated VS problems, even though I needed to move onto it in order to be able to work on C++ projects that had already started using C++17.
> You're also assuming that somehow spending more time with unstable language features will result in fewer problems, which is something where we know you're wrong. Because we've seen what happens when a longer cycle exists, we know your claim that the compilers will be less buggy is exactly wrong.
Again, I was not saying compiler bugs increase when you rush the standard. See above.
0 - https://www.boost.org/doc/libs/1_70_0/libs/python/doc/html/i...
The concept is to use MicroPython on embedded devices but, if performance is lacking, drop into C to create a module that can be easily accessed from MicroPython.
I've found this to be an exceptionally productive embedded development environment!
[1] https://2019.pycon-au.org/talks/extending-micropython-using-...
but the main problem ist tthat alignment is _not_ part of the typing system. ( alignas vs alignof )
butt, to put it more general: 90% of your performance is in memory access and compilers are rubbish optimizing those, they are however getting increasingly good at the 10%. see also https://www.youtube.com/watch?v=rX0ItVEVjHc for realworld examples/exploration
I don't understand what you are trying to say here? Why not just use
struct alignas(16) float4 { float v[4]; };
You can even put both __declspec( align(16) ) and __attribute__ ((aligned(16))) in the same place if you want to have a fallback for older compilers.It's especially nice that Rust has kept this basic attitude from C/C++ and in fact strengthened it a lot and aligned it with modern trends, even as it got rid of the annoying "core dumped" part almost in its entirety.
(The latest tagline of Rust is "A language empowering everyone to build reliable and efficient software." Do notice the everyone part, and especially the empowering bit - as opposed to letting even novice developers hobble themselves with substandard, bootcamp-level software-dev practices!)
In rust, we have additional reasons, and that’s because it’s not
let name: type = expression;
It’s let pattern: type = expression;
Patterns offer more power than simple variable declarations. The names may not correspond 1-1 with the type, because you can create multiple names by destructuring more complex typesThey should have done what java did. Copy C++ syntax, only change it when needed. I've ported over java code where 2/3 of lines are nearly identical.
The async debacle is a great example of this. They settled on weird syntax instead of doing what every other language does, because of some holier than thou acedemic snobbery. If every other language does it that way, it would have worked fine in rust.
> because of some holier than thou acedemic snobbery
The async syntax was one of the most widely discussed issues in Rust development, and ergonomics concerns were key in what eventually was chosen. It's very misleading to describe it as your comment does.
Re: parent comment, the Rust programming language book (free online) has a very nice section describing OOP-like patterns in Rust - as it turns out, the "good parts" of OOP are very nicely supported, and in a far simpler, more orthogonal way than what you get in C++. This means fewer dark corners in the language and something far easier to work with overall. Just because it may be different from what we did back in the 1990s, doesn't make it wrong!
However, I went through the book recently and I'm quite annoyed that much of the syntax differs needlessly from C like languages. It massively increases the cognitive overhead for someone coming from Java, C++, C#, C-like language world.
Rust is a systems language, it doesn't even have a runtime. 90%+ system level work is done in C-like languages. Rust syntax differs in countless pointless ways. I'm not saying it's wrong, it's just different in ways that don't matter from all other popular systems languages. Which is dumb.
Things like "fn" instead of "function" and async syntax make Rust difficult to adopt by the target user base. Why fn? Is saving 6 characters worth confusing everyone?
And it decreases the cognitive overhead for someone coming from Python, Ruby, Go, heck even Haskell or Ocaml. "Cognitive overhead" over a simpler, more elegant syntax (and I think I've made the case that Rust typing syntax is simpler once you move beyond trivial cases!) is a temporary issue anyway - you get used to it very quickly. What I find quite puzzling here is the particular issue you're complaining about, wrt. the C/C++ type declarations. You actually like having to write out things like "template" and "typename"? Now of course Rust syntax is rather C-like in other ways, but still!
Why should a new language inflict this horror on its users:
let i32 (*foobar)(i32, i32) = add;
When it can do this instead:
let foobar: fn(i32, i32) -> i32 = add;
Btw the function syntax is:
fn foo(x: u16, y: u16) -> u32
If it were more C++ like it'd be: u32 foo(u16 x, u16 y)
Which is less keystrokes if anything.Haskell, Visual Basic, Scala, F#, Go, (Rust), Kotlin, TypeScript, Swift.
Even c++ allows you to put return types on the right. Why? Because putting the types on the right allows return type deduction. I think it's better to have a single syntax (all types on the right) rather than 2 syntaxes like c++ has.
https://medium.com/@elizarov/types-are-moving-to-the-right-2...
u32 foo(u16 x, u16 y)
auto foo(u16 x, u16 y) -> u32
IIRC there are contexts where the former does not work and the latter is required, which I believe means that ML-style function headers are strictly more powerful in C++ than the original C-style function headers. template<typename T, typename U>
auto add(T t, U u) -> decltype(t + u)
{
return t + u;
}
C++ is notoriously hard to parse. Consider the "most vexing parse", or the fact that refactoring tools for C++ are always flakier than their Java / C# / Go equivalents, or the fact that https://cdecl.org/ exists. New languages in the "expressive, high performance" niche cannot continue to be held back by C++ syntax.Disclaimer, I'm speculating that's what they meant, because I feel the same way. It's more common(for me) for Python to refuse to run than to run anyway and later crash, compared to C++. That said, Python isn't a good analogy there...Rust or Golang are more likely to refuse to run for bad code instead of running anyway and crashing later.