C++: Is It Really a Cruel Joke? (2003)
webhome.phy.duke.edu
webhome.phy.duke.edu
The language takes a lot of flak but I think it is quite good given the constraints it has and the modern version is an amazing leap forward in ease of use as well.
I am not a C++ programmer, but I found that book extremely interesting and a pleasure to read. I think I have publicly said so before, but I wish there were books like this for more programming languages. There's a couple of fascinating HOPL talks on languages like Lisp and Lua, but due to brevity, they are not nearly as detailed and in-depth as this one.
IMHO the STL is one of mankind’s greatest accomplishments.
Many high performance projects use vector but few other containers.
If the STL had a lesson to teach us it should have been "iterators everywhere, including for your own algorithms and container types." Instead most people learned "C++ has all the containers you need built in, throw away your performance tricks and let it call malloc a million times."
As an example, STL unordered-maps have fairly strict requirements around iterator invalidation and that results in indirection that do affect performance. It's pretty easy to make a "faster" unordered_map that tosses that requirement (as many do).
Right. And then it is actually a lot simpler to simply use pointer + size pairs [1] instead of std::vector. Changing to explicit allocation was the best decision I've made. I now find myself not longing for any C++ features anymore at all. I haven't needed anything besides a little allocation wrapper [2] and maybe a string-to-hash map since.
[1] Or n pointers + 1 size for parallel arrays, indicating that it's a bad idea to glue pointer + size in the first place.
[2] https://gist.github.com/jstimpfle/562b2c3e9fe537e378351bb9d5...
string-to-int hash map
STL really is a cruel joke.
is it ok to mutate a data structure while an iterator to it is live? It depends, which makes it much less abstract and generic than it could be (and that people believe it is)
Error messages are useless. Compilers can and do (recently) help with that, but the main issue is the convoluted STL design in which every instantiation’s type name actually takes a full 80x25 screen to spell out.
STLs primary objects are individual iterated elements, which does abstract over pointers (as was stepanov’s intention) but are at such a low level of abstraction as to be onerous.
Modern C++ is slightly better.
To say it’s a problem is misleading at best.
Other languages and runtimes use the notion of an iterator or cursor that is alone enough to perform all looping-like operations on the container including efficient erase() and can cheaply (so it is ok to have that in production) or even with good optimizing compiler at zero cost provide protection against container mutations.
Modern c++ shows there was/is room for improvement, but it does not fundamentally simplify the language imo.
[1] https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
2) Even with cheating in scare quotes, name-calling is not OK.
I say this from personal experience; in one case I was doing timing studies to solve performance problems, and wound up fooling myself by measuring the wrong thing!
In this case, is it fair to use SIMD intrinsics? It depends on what you're trying to measure. I think that's why "cheating" is in scare quotes, because what would be cheating in one context might be useful information in another.
For instance, if C++ is providing SIMD intrinsics, it's going to beat other languages, and if I just want current performance statistics, that's the question I want to answer.
If the question is, "what's the overall quality of the code delivered by the compiler / optimizer" then using specific tricks doesn't give me a good answer.
dralley's comment does not do that.
There's nothing difficult here: simply say that those X of N leading C++ programs use SIMD intrinsics, when the corresponding Rust programs do not.
dralley might even say that SIMD intrinsics have been available in Rust nightly for years.
dralley might even say that someone has contributed a Rust program that does use SIMD intrinsics, but that program was slower than other Rust programs:
https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
Perhaps if C++ [or Rust] is providing SIMD intrinsics that is not in-itself a magical silver bullet.
And then there are all of the safety features of Rust; while there is a learning curve, it is much easier to learn the concepts of lifetimes and ownership when the compiler is helping to enforce proper usage.
I have almost 30 years of C++ experience and about 6 months of Rust. My gut feeling is that the languages are equally difficult to understand but that the experience of writing code in them is very different.
I'm sure Rust will have its own set of surprises as I keep using it.
But I can't believe they will be nearly as bad as those that C++ comes with. ;)
I always speculated that Jobs went up to the engineers, "hey, we use a lot of Objective C in OS X, right?"
"Yes, sir, Mr. Jobs."
"Well, everyone else, namely Adobe, is using C++ and we need them writing apps for the Mac. We've gotta have those apps. So we're going to need to support that."
"Um, yes sir, Mr. Jobs, we'll get right on that."
He leaves, and they look around nervously. "He's pulling our leg, right?"
As I've said quite recently elsewhere, C++ is an archaeological dig of a language. There are something like 4 major strata. If you would learn and use C++, it behooves you to pick a particular style, then stick to that. (RAII and smart pointers are very useful!)
There's something called the Taligent coding standards, which were once popular, then later castigated as turning C++ into "a poor man's Smalltalk." Small teams can write some dandy code using that style. Everything can fall apart at scale, however.
(EDIT: Here's an example from elsewhere in these comments: https://news.ycombinator.com/item?id=17463569 )
I've talked to some of the people at Mentor Graphics who were there during that period. The company basically went from #1 in the industry to #3 or so during the course of the C++ refactor (and the EDA industry isn't exactly big). Bjarne Stroustrup showed up at the company now and then because the company was such a major early adopter. Inheritance chains were 5 or 10 classes deep. A full build took a week. The company hosted barbecues on weekends and invited the employees' families so they could see each other.
I only worked there somewhat later, so I just heard the stories from people who were there at the time, and only after I had been there a while. Take some old-timers out to lunch now and then, you'll learn a lot. I ended up leaving, I was more than a bit frustrated by the organizational culture and the build system my team used was by far the worst I have ever seen in my entire life.
But Oregon is an awesome place to live, the salary was good, and the hours were normal.
30 years later, I still run into the same problem regularly. I'm not sure why, but this seems to be an anti-pattern that everyone needs to learn about the hard way.
If it's anything like when I was in school, as soon as the curriculum trots out its first object oriented language, you get a lecture about how is-a relationships are the greatest invention since the compiler, and deep inheritance hierarchies are both the most practical and the most morally righteous way to organize your abstractions.
(Meanwhile, ironically enough, I'm not sure I've ever heard a CS instructor even mention the Liskov Substitution Principle.)
https://gigamonkeys.wordpress.com/2009/09/28/a-tale-of-two-r...
Of course this was mid-90s when both the language and especially the compilers were quite different from now (and the compilers very buggy indeed).
What were 'normal' hours for a programmer in the 1990s? Did you guys work 9-5?
That said, I think the complaint maybe comes down to coding style and architecture of the thing they're coding. They seem to make a joke we'd maybe more closely associate with Java than C++ these days. Also Microsoft's C++ style is awful. So if that's the only experience you have with C++ I would be hard-pressed to blame you for hating it.
> And, as I said before, every C++ programmer feels bound by some mystic promise to use every damn element of the language on every project.
https://www.boost.org/doc/libs/1_61_0/libs/smart_ptr/smart_p...
The macos kernel driver interface, IOKit, is C++ too.
-- Bjarne Stroustrup [1]
It's because they are not used in equal proportion.
A lot of people seem to take pride in memorizing these quirks, but they're just that. There's nothing fundamentally interesting about them, and they're just mental clutter at the end of the day.
We should strive to have well designed languages that are optimized for developer experience. Accepting poor design decisions just keeps perpetuating the problem.
[1] https://insights.stackoverflow.com/survey/2018/#most-loved-d...
1. Nested templates closing brackets conflicting with `>>` operator, necessitating `> >`. This was fixed in C++ 11.
2. Syntax for declaring an automatic variable conflicts with the syntax for C function type: `Thing mything();`.
Those two certainly seem to be "unforced errors" where the language is simply stepping on its own feet for no good reason, and one of them hasn't even been relevant for over five years.In all other cases in my experience, investigating the rationale behind a particular quirk has led to a fairly interesting reason; a difference between the heap and the stack, say, or the language giving you the option to not do some work that may be expensive and unnecessary. For example, beginners often are surprised and annoyed that `remove_if()` doesn't actually remove anything and they need to call `erase()`. But most STL algorithms work on a pair of start and end iterators and you can simply work with the new "past-the-end" iterator returned from `remove_if()` allowing you to combine or omit the calls to `erase()`. This is certainly quirky but its not "mental clutter": there actually is a fairly interesting reason for the API being designed this way, rooted in the zero-overhead principle.
In my experience that has been the rule, not the exception - taking the time to understand the "why" behind a given quirk usually results in being forced to admit to yourself, "yes, I see; that is the only way it could have been designed as a zero overhead abstraction. The 'simpler' alternative I had in my head would require some overhead to implement." I think that's the reason why so many people on this thread who have read "The Design and Evolution of C++" change their mind and come away with praise for the language - because it lays bare the logic behind many of those design decisions.
I would be interested to hear which quirks in C++ you view as mental clutter and/or design mistakes. As far as I can tell, most of C++'s usability problems come from it being too carefully designed and too backwards compatible. And after witnessing disasters such as Perl 6, I'm not sure "backwards compatible" is really a "mistake" per se.
C++ goes completely against the principle of least astonishment.There is a huge amount of mental overhead to reading and writing code in it. All that distracts you from the problem you're actually solving and directly translates into long development times, defects, and maintainability nightmares.
I don't think the complexity of the language ultimately justifies the goals it's trying to accomplish.
OOC, how long ago did you witness this "disaster"? Perl 6 is doing very well, thank you.
> ... You know, when we had our first C++ compiler, at AT&T, I compiled 'Hello World', and couldn't believe the size of the executable. 2.1MB
> Interviewer: What? Well, compilers have come a long way, since then.
> Stroustrup: They have? Try it on the latest version of g++ - you won't get much change out of half a megabyte.
So, for grins, I did, with the gcc7 port from macports, which is GCC 7.3.0.
-rwxr-xr-x 1 ssta staff 8968 Jul 5 10:40 hello
The C version, using printf instead of (gasp) std::cout, clocked in at 8432.
#include<stdio.h>
int main(){
printf("Hello World");
return 0;
}
>5649591 -rwxr-xr-x 1 user user 8288 Jul 5 21:49 a.outWhat am I doing wrong?
I wonder what would be the author's opinion of Rust
This line appears to be a snide remark at class-based OOP, for which a regular criticism is that real-world projects rarely slot neatly into the sort of "Cow is-a Mammal is-a Animal" taxonomical hierarchy that is used to teach class-based OOP in school. Rust doesn't have classes or taxonomical hierarchies, and encourages struct-first POD design (similar to C), augmented by traits which provide shallow has-a relationships (composition) rather than deep is-a relationships (inheritance).
http://www.stroustrup.com/whitespace98.pdf
Generalizing Overloading for C++2000. Bjarne Stroustrup. AT&T Labs, Florham Park, NJ, USA.
I liked Borland Pascal, and I know Delphi is kind of ticking along, but I'd rather invest in a language that is growing.
Swift looks nice but still seems too Apple focused.
Don't want to start a war, just open to some tips on the ecosystem..
If you look at modern C++ libraries (like boost), you will see a lot of templates and free functions, and not a lot of inheritance or dynamic polymorphism.
I mean, people were already calling for more templates and free functions in 1997. It's not modern by any stretch of mind, it's just normal C++.
Rust maybe. Swift have at least a valid alternative to move to other platforms:
I wish Apple get smarter and put swift for windows and better linux.
Another alternative is D, which is basically C++ minus a few warts, the option to have a somewhat faster compiler (all modern C++ compilers are very slow) but it wasn't made yesterday so it has accumulated its own cruft (most common being that most of the new "flags" for declarations being in the form of "@stuff" instead of just "stuff" even though some older stuff being just "stuff" so things look a bit messy - in a similar vein, the new supposedly best practice, especially for libraries, is for functions to be pure and nogc, but this isn't the default so you have to put the declarations everywhere). Still, it is the most obvious choice for someone who wants a better language than C++ without changing too much.
There is also Rust, but i don't know about it.
C++ with Qt.
Every year I keep looking to see if a language will become available that will be able to replace Delphi, and there have been some close candidates, but nothing quite there yet. The close candidates right now are: AOT-compiled C# and Go. If you like the OOP in Object Pascal, then the type handling in Go will probably seem wonky to you, and as far as I know, AOT C# isn't quite ready, but I may be wrong on that front (it's hard to find information on the AOT progress with C#) and someone else can chime in with more information. I cannot understate what a game-changer AOT C# will be on multiple platforms, especially if they can keep the binary size down.
If that’s what you meant, C# fits quite well. The language is safe, very high-level but has easy ways to use C interop or pointer arithmetic, performance is adequate for many practical applications, asynchronous IO and multithreading are IMO best in the class, tons of libraries, good documentation, many users.
The main downside is limited options for cross-platform GUI. On windows it’s very good, on mobile platforms OK, for the rest of them (Linux esp. embedded, OSX) there’re no good options that I know of.
Another one is runtime size, current version is around 25-30 MB. No installation is required, but for some applications that’s still too much.
It supports polymorphic functions like other OO languages.
While I find functional patterns more useful in Rust, that doesn’t prevent OO design where it’s desirable.
I work on a very similar type of application that manages async workers who process large distributed NLP tasks. Writing it in Cython was extremely easy, because for the modules that have zero need for static typing, such as the part using async/await in Python 3, or when we supplement with gevent, I can just write those parts in plain Python and it’s quite a bit easier than Nim or Cython or whatever else, while still having great performance from those tools’ low-level implementation.
Then for the parts that do possibly benefit from static typing and compilation (unlike the async layer), I can have precise module-level control over what has a C-level implementation and if or how it interacts with anything in Python.
The inability to separate the two situations in Nim (as with many statically typed languages) just doesn’t work out well enough for my use cases.
In fact, I’d even go as far as to advocate that in today’s language landscape, if you want to write a new greenfield project in C or C++ for performance reasons, it’s unequivocally your best option to write the whole thing in Cython, and avoid what you might call “premature static typing optimization” by profiling and leaving the things with no bottleneck in Python.
Can you elaborate on the reliability part? I've spent a lot of time grokking Nim specifically to be able to make good judgments about whether there are use cases in which it would be a better choice than Cython, and from a reliability point of view I have not noticed anything that would distinguish Nim from any other language. I can agree that Nim's syntax is nicer than many other statically typed languages, though the language design has some warts with `result` and `discard`, etc. But I can't see any reason to believe it is 'more reliable.'
> "I ended up eliminating all Python code from my back-end."
While I can't know the reason for this in your exact case, generally this seems like a very suboptimal thing to do. Python has a much richer set of libraries, testing utilities, etc. It is a language with a huge community of users and developers, and much more likely to be a known language for someone new who joins the project. If a system was working well and someone proposed to refactor away a solid base language like Python, that would almost always be a crazy choice, regardless of any positive aspects of the targeted new language. It's similar to why you should rarely throw away old code that has meaningful tests. You can slowly refactor it little by little, but wholesale switching to something else is usually evidence of wrong engineering priorities, especially when the something else is a 'latest and greatest' kind of new language or tool, like Nim is.
> "Because Nim is statically compiled I can deploy pieces of it anywhere just like a compiled C program, without dependencies, and it means a lot in my particular situation."
This can also be done with Cython, using the options to embed an interpreter... and there are various other third party tools that allow you to create thick binaries for combined Python programs as executables, including runtimes and dependencies. To boot, you definitely should be managing the deployment of some binaries with proper dependency management practices. So really, if you're already using dependency management techniques for the binaries, the minor extra work to maintain Python environments and dependencies would almost always be pretty trivial, with a huge family of tools (pip, conda, pipenv, virtualenv, etc.) and endless tutorials on the community-developed and mature best practices for packaging Python programs.
I would be curious to know more details about a project where it was truly advantageous from a productivity and deliverability point of view to rewrite the backend to move from a stable and mature ecosystem like Python to a relatively younger and less mature system with Nim specifically to gain a benefit somehow related to ease of deploying pieces of the code to different locations. The details just don't sound like they could possibly be in favor of using Nim in a case like that.
What I meant by reliability is personal. The stuff I learned when reading and experimenting with Nim in one day sufficed to do practical things in my daily work. Any new information was found easily and I could keep developing (vs. some years ago when I was learning Haskell, after the first joy with the "cleanness" of the syntax, the systems programming part down the road became a bear. I had to go through yet another learning curve to digest that). May be reliability is not the word, but its just that Nim didn't let me down even though only a very limited time was invested to learn it for my purposes.
> Python has a much richer set of libraries, testing utilities ..
Yes indeed. If an off-the-shelf numpy package sufficed I would have stuck with it. For recent numerical work (this is where I tried Nim first, graph theory, matrices where you have observed non-standard sparseness, ie. not a Toepiltz kind "standard" sparseness, and these you use to your advantage by coding it yourself). Even if I find an exactly needed package from the community, I have to read the sources to know how it is coded. Subtle details in implementation cannot be fathomed from verbal documentation as it impacts rate of convergence, memory consumption etc. or perhaps a bug your use case uncovered! So during prototyping and validation you use python or whatever to help with the exploring, test cases etc. Once you know exactly how you need to structure the algorithms, I code With Nim and I will know exactly whats in it. Nim coding has been easy, and the performance unusually good.
> with a huge family of tools (pip, conda, pipenv, virtualenv, etc.)
In my point of view, I'd rather not carry these many things to develop and deploy to maintain and manage processes at many distributed sites. On my local machine, yes. The initial development install of Nim anywhere (takes 3 minutes, no admin) has all the tools for the build, unit test, package, integration test, and you can run compiled binary anywhere, on systems supporting just the key data/network dependencies that the application demands...traveling light.
> though the language design has some warts with `result` and `discard`, etc.
Why do you consider these to be warts?
< https://nim-by-example.github.io/variables/result/ >
Even just needing to account for that mental gymnastics about declaring a new result variable is, I think, not forgivable.
The bigger issue though is that initialization of the return variable is implicit, which is inherently problematic. This is especially troublesome because type constructors in Nim are essentially always separate factory functions. So you have to remember to manually call a particular constructor or else `result` might be just an improperly initialized skeleton of your data type.
For example, I might have some type called MyType and a proc with return type of MyType. I explicitly don’t want the proc to initialize `result` to an empty MyType behind the scenes, for whatever implementation reasons about MyType (a common example is a type that ought to be initialized with the acquisition of a resource and should never exist in a partially initialized state in which the resource acquisition hasn’t been attempted yet, and could possibly fail later).
If I only want it to be initialized from a special constructor like mkMyType(), then in Nim, I have to code around this limitation by making it a void proc, and passing in an appropriately mutable reference.
In other words, to avoid possibly inappropriate return type initialization, I am forced to revert to poor C-style void functions all over that mutate placeholder inputs by convention, which undermines a lot of things Nim tries to do to improve clarity about pure vs impure procs.
I don’t have time to go into why discard is a bad design idea right now, but hope to come back and add more later.
This is simply an explanation for newcomers. It's really not something that's a "severe problem", just something to be aware of.
> The bigger issue though is that initialization of the return variable is implicit, which is inherently problematic. This is especially troublesome because type constructors in Nim are essentially always separate factory functions. So you have to remember to manually call a particular constructor or else `result` might be just an improperly initialized skeleton of your data type.
This really isn't an issue when you can do this:
import options
type
MyFile = Option[int]
proc getFile(): MyFile =
# Oh no, I didn't initialise it...
discard
echo(getFile()) # -> none[int]
You can also use `ref T` and achieve a similar effect: an explicit "empty" state. So there is no weird semi-empty state problem here.I would really like to hear why you think `discard` is a bad design idea. I honestly cannot even imagine a reason as I consider this to be one of the best features of Nim.
It’s not reasonable to suggest you have to code past this intrinsic limitation everywhere by muddying all your function signatures to take Option types and adding extra logic to pack or unpack values from Option types all over... to solve an initialization problem!
Basically, discard & result make Nim a nice language if you are programming alone, and you know & intuitively understand the conventions being used or you can control manually wrapping stuff in Option for a bunch of type signatures or whatever and you can enforce it how you like it.
But when writing code for other people to interact with, the implicit return type initialization creates weird ways of coding around it that are not clear or common sense for other people, and then mixing void and the use of discard makes it super unclear when or why it’s useful to ignore the return type in some context, instead of it having been actually designed as a void function (and for this to have a proper type).
This comment on this Nim issue gives a good example of what I mean, < https://github.com/nim-lang/Nim/issues/7370#issuecomment-376... >.
But generally, I think it just speaks badly of discard-style thinking. Write void functions to communicate side-effectfulness. Don’t mix concerns about a side effect and an optional return value and assume people will get your meaning and know when to use discard. That is more like coding for the function author’s benefit instead of coding for readers, users or other contributors.
I’m saying when you program alone, you know when to use discard on your otherwise value-returning function. Other people don’t, and the use of the return type actually suggests the opposite. That you should intentionally invoke that proc for its return value.
> “That's against "nice if you are programming alone" as much as it can get.”
I don’t understand this claim. Nothing about the formal definition of a language is for or against being “nice if you program alone” — rather it is what patterns of usage does it encourage or facilitate.
It’s like “C++ without exceptions”. The formal implementation is just some factoid of the language, but the usage that arises around discard is a bad anti-pattern in terms of communicating intended usage and whether / when to rely on side-effects.
Also many languages use a very standard convention of assigning underscore to parts of a result value to be ignored, and discard has no clear advantages over this in my mind.
I've heard of this, but does it happen?
I did my degree back in the '90s at Drexel, I don't recall there being any prescribed language... I did some systems stuff in C, some AI courses in LISP, a concurrency course in Java, some stuff I don't recall in Perl, some math courses used Maple, and there was a bit of shell and some familiarization with the Solaris boxes, etc.
I don't think I ever actually took a course on a specific language, you were just supposed to RTFM and figure it out.
C is a space ship, Java is a plane, and C++ is an amphibious SSTO. It's messier in design and trickier to pilot, but you can do more with it!
Support has been slow, but even the non-GCC embedded toolchains have been improving substantially over the last few years.
Some folks will just use a language without thinking about its downsides. If you use language X for a long period of time, you can become blind to areas where it is wasting your time.
Again, I do agree that the tone could often be much more civil, but I would hate for experienced programmers to stop pointing out things that bother them about their programming languages. Much of the time it is not just about personal taste -- it is about real problems with the language that have a real cost.
But that's not what this article is, and I think we all know the tendency I'm talking about for people to spend more energy and feel more comfortable trashing things than talking about what they like.
Maybe the discussion will identify some problems with a thing. There's an infamous piece "PHP is a fractal of bad design" that I found very insightful.
But, honestly, it's mostly because it's entertaining.
- is the language suitable for the domain?
- is library/community support mature?
- are there enough developers?
- who in the company will support it? It's obvious from HN topics that we are obsessed in language topics.
- it also reflect some part of design philosophy, and it could be good or bad for your project depend on the case.
I've personally never grown comfortable enough with C++ to get to a point where, were I to be the one calling the shots, it would ever be my first choice. But I also recognize that it's dominant in certain spaces for a reason.
At the same time, I have a lot of sympathy for people who prefer C over C++. There's a lot of cognitive overhead involved in understanding the semantics of an object-oriented language, especially a big complex one like C++ or Java. And complex languages do have a tendency to beget complex implementations, even when you're working on a project that could be small and simple.
That reason usually is: "no other compiler were available" or "no other choices at the times".
Sure, there are an uncountable number of other factors, but picking the wrong platform/language can be fatal.
If you're a "body shop", then choosing an unpopular language could be fatal, whereas if you're building a specific software product that needs to do certain things on certain platforms, then choosing the language becomes less of a popularity contest and more about how it can help you finish the product quickly with the highest level of quality and performance.
I think, more often than not, language choice is like golf club choice. Different pros have their preferences, but a pro can play a good game with any set of clubs. A novice will blame the clubs for a poor game.
Using C (maybe C++ ?) may be great for micro controllers, a BIOS, a stage 1 bloat loader, OS kernel and device drivers.
If you're writing application software, and probably even server software, you should be using something higher level and a bit more abstracted away from the hardware. Not necessarily by much. But definitely more than C / C++.
It might matter if you used COBOL, though. Or even C++.
Would you believe I once saw backend service written in PHP4? And not a small one either. It was just the only language the 2 original authors knew... Apparently they had a bit of memory problems because substr (or similar) leaked a few bytes, which over a course of few months amounted to quite a lot of memory.