Was Linus Torvalds Right About C++ Being So Wrong?
Nerval's Lobster writes: Perhaps the most famous rant against C++ came from none other than Linus Torvalds in 2007. "C++ is a horrible language," he wrote, for starters. "It's made more horrible by the fact that a lot of substandard programmers use it, to the point where it's much much easier to generate total and utter crap with it." He's not alone: A lot of developers dislike how much C++ can do "behind the scenes" with STL and Boost, leading to potential instability and inefficiency. And yet there's still demand for C++ out there. Over at Dice, Jeff Cogswell argues that C++ doesn't deserve the hatred. "I've witnessed a lot of 'over-engineering' in my life, wherein people would write reusable classes with several layers of inheritance, even though the reusable class wasn't actually used more than once," he wrote. "But I would argue that's the exception, not the norm; when done right, generic programming and other high-level aspects of C++ can provide enormous benefits." Was Linus going overboard?
Could we stop having Dice articles submitted by Nerval's Lobster? Why not fully disclose that the story was submitted by the corporate parent of Slashdot?
I just looked through Nerval's Lobster's last 15 contributions. All were article submissions, Nerval's Lobster doesn't appear to comment on anything. Here's the list:
Every single one of them is from dice, though only a few of them actually make that explicit (the non-explicit ones are marked [Dice*]. A large fraction of them are related to human resources and hiring people, which I've marked [Hiring]. So its like Nerval's Lobster is using Slashdot as advertising and recruitment channel for Dice.
The average quality of these submissions was very low in my opinion - lots of vacuous pointy-haired-boss buzzword stuff. Very un-nerdy. How did these get through submission moderation? Were they even subjected to it?
C itself has so many pitfalls. For the best tour review the underhanded C contest. "features" like automatic concatenation of consecutive character strings means that if you leave out a comma in a list, the adjacent array element entries are concatenated rather than throwing a syntax error. That list will now not match the declared array size (one short, so there's a null or random pointer in the last element) but the compiler allows initialization listed mismatched to the array sizes. Character strings have to be declared one longer than the initialization string length (room for the unstated \0) but are accepted by the compiler if they don't giving an unbounded string length.
it's mind boggling to realize that
int (*int)[20];
int *int[20];
are different things.
the number of different ways an array argument in a function can be written makes code hard to grasp: is it a pointer, an array, a reference? many work alike but then fail in different ways.
The most common of all pitfalls and hard to read codes are the in-line initializations that pop up in function arguments and what not. this leads to classic blunder of writing = when you mean ==.
Perhaps the most insane thing is that If you declare an external function with the wrong prototype then any mismatch in the argument count leaves or takes something off the stack. Holy cow..... I mean what the hell? Why would any language ever ever ever let you leave a orphan argument on the stack, or worse pop one off that was not yours? This is very useful for the underhanded C folks however.
While I know there's little love for fortran, it's worth noting that none of those things is even possible in Fortran, so its an existence proof that there's not any necessity for those to exist and that it doesn't limit the power of the language to remove them. It's very fair to say that no simple typo will ever compile in fortran (yes very complicated offsetting typos can compile).
Some drink at the fountain of knowledge. Others just gargle.
Right now I'm doing just C and assembler (and the occasional scripting in something else). Sometimes it feels a bit archaic compared to a stripped down C++ style I'm more used to, but on the other hand there just aren't as many political fights over style so it's less stressful. The interesting thing about C is that newer standards don't tend to lots of new and experimental features, which is sort of the norm with C++. C tries to be stable.
Also a big part of my job when doing C++ was decoding the obtuse error messages for coworkers, and that almost never happens with C...
You can argue about whether C++ is a horrible language (I lean toward "yes") in itself, but the libraries are what really push it over the edge. STL is hands down the worst collections framework I've ever encountered. Consider just a few examples of how you do some common operations with it, compared to doing the same things in Java and Python.
1. Check whether a string s ends with a suffix t.
Java: s.endsWith(t)
Python: s.endswith(t)
C++: s.rfind(t) == s.size()-t.size()
2. Check whether a collection c contains an element e.
Java: c.contains(e)
Python: e in c
C++: c.find(e) != c.end()
3. Split a string s into tokens based on whitespace.
Java: s.split() ... do you really want to know? Ok, check out http://stackoverflow.com/quest.... There you will find dozens of proposed solutions (many of them quite indecipherable), along with lots of debate about which one is best. The top voted solution has a comment on it (with several hundred votes) saying that it's a bad solution and you shouldn't use it.
Python: s.split()
C++:
Doing even really basic, common operations with STL requires way too much work and produces absurd, hard to read code.
"I'm too busy to research this and form an educated opinion, but I do have time to tell everyone my uninformed opinion."
Only without optimization flags enabled. Otherwise the lambda will be inlined in most cases.
BZZT! Wrong! Think: what happens if a and b are local variables and the function creating the thread returns before the lambda runs? They must be copied to somewhere that is not destroyed by the caller returning. That copy is not in the correct location because they are created before the thread stack, so another copy is unavoidable. The only way to fix it is to make do_something take const references. Though it is true that if do_something was inline it would probably fix it.
Show me the code
Yes it is ugly and I never claimed otherwise. The problem is that C++ compiles into the equivalent of this and it is hidden behind the scenes. Here is is pretty obvious that I must not pass a pointer to a or b, not so clear in C++:
#include <pthread.h>
#include <stdio.h>
#include <malloc.h>
#include <unistd.h>
void do_something(int a, int b) {
printf("do_something %d %d\n", a, b);
}
typedef struct {
int a, b;
} lambda_args;
void* lambda_run (void *p) {
lambda_args* v = (lambda_args*)p;
do_something(v->a, v->b);
free (v);
return 0;
}
void thread_do_something (int a, int b)
{
lambda_args* v = (lambda_args*)malloc(sizeof(lambda_args));
v -> a = a;
v -> b = b;
pthread_t thread;
pthread_create (&thread, 0, lambda_run, v);
}
int main()
{
thread_do_something(1, 2);
thread_do_something(3, 4);
sleep (1);
return 0;
}
BZZT! Wrong yourself! And let this be a lesson to you to not be so haughty when replying. What you describe never happens. The lambda is built synchronously, but called asynchronously.
To prove it, try out the following code:
Compile it with "g++ --std=c++11 -S -O4" and check out the .S file (compare it to the -O0 version too). In the -O4 version you'll see the following:
You see that? It's pushing the local values directly onto the call stack. There is no intermediary step for the lambda.
Okay, so maybe my test case was too simple? Let's complicate it.
Try it again with -O4 with the if set to 0 or 1 and compare the two versions together. You'll see that they're exactly the same. In both cases, main is:
"Are you hungry? I haven't eaten since later this afternoon." -- Primer