37

After reading this famous rant by Linus Torvalds, I wondered what actually are all the pitfalls for programmers in C++. I'm explicitly not referring to typos or bad program flow as treated in this question and its answers, but to more high-level errors which are not detected by the compiler and do not result in obvious bugs at first run, complete design errors, things which are improbable in C but are likely to be done in C++ by newcomers who don't understand the full implications of their code.

I also welcome answers pointing out a huge performance decrease where it would not usually be expected. An example of what one of my professors once told me about an LR(1) parser generator I wrote:

You have used somewhat too many instances of unneeded inheritance and virtuality. Inheritance makes a design much more complicated (and inefficient because of the RTTI (run-time type inference) subsystem), and it should therefore only be used where it makes sense, e.g. for the actions in the parse table. Because you make intensive use of templates, you practically don't need inheritance."

15
  • 6
    Some of the more nasty errors you can make in C/C++ are mostly because of the C heritage... read undefined behaviour, manual memory management, etc. Also, the prof's advice seems bogus/wrong (to me, who isn't an C++ expert) - template instantiation should yield a normal class with vtable for virtual functions, right?
    – user7043
    Commented Feb 7, 2011 at 20:12
  • 8
    You're either mis-remembering what your professor said, or he had no clue what he was talking about. Derived classes don't generally need to use RTTI (AKA reflection) to look things up. If they're using virtual methods, the code might need to do a vtable lookup for the dispatch, but that translates to a single ASM instruction on a lot of processors. Because of caching issues it can slow things down by a certain amount, but you're unlikely to ever notice the overhead under any but the most demanding use cases. There are plenty of good reasons to avoid C++, but vtable lookups aren't one of them. Commented Feb 7, 2011 at 20:17
  • 5
    @FelixDombek: Stated so generically and applied across the board, that quote from your professor just shows a huge amount of ignorance. When your design needs runtime polymorphism of some kind, using virtual functions is often the best choice; when you don't need it, don't use it: you don't need all methods to be virtual just because you use derived classes, for example.
    – Fred Nurk
    Commented Feb 7, 2011 at 20:26
  • 5
    @Mason Wheeler: RTTI contains information on the type, enough to be able to determine whether a dynamic_cast should succeed or not, and few other things, but reflection covers much more, including being able to retrieve information on member attributes or functions, that is not present in C++. Commented Feb 7, 2011 at 20:41
  • 5
    The professor's comment is somewhat deceiving, as inheritance and virtual functions aren't a big performance hit. The advice to use inheritance sparingly is good, but it's more of a problem of program structure than efficiency. Inheritance, particularly with protected members, is about as close a coupling as you're going to get, and if you don't need it you shouldn't use it. Commented Feb 7, 2011 at 20:51

13 Answers 13

72

First of all, his rant is really nothing BUT a rant. There's very little actual content here. The only reason it's really famous or even mildly respected is that it was made by the Linux God. His main argument is that C++ is crap and he likes to piss C++ people off. There's of course no reason at all to respond to that and anyone who considers it a reasonable argument is beyond conversation anyway.

As to what might be gleamed as his most objective points:

  • STL and Boost are utter crap <- Whatever.
  • STL and Boost cause infinite amounts of pain <- He's purposefully over-exaggerating, but then what is his real statement here? I don't know. There's some more than-trivially difficult to figure out issues when you cause compiler vomit in Spirit or something, but it's no more or less difficult to figure out than debugging UB caused by misuse of C constructs like void*.
  • Abstract models encouraged by C++ are inefficient. <- Like what? He never expands, never provides any examples of what he means, he just says it. Since I can't tell what he's referring to there's little point trying to "rebut" the statement. It is a common mantra but that doesn't make it any more understandable or intelligible.
  • Correct use of C++ means you limit yourself to the C aspects. <- Actually the WORSE C++ code out there does this so I still don't know what he's talking about.

There's no intelligible argument being made about anything. To expect a serious rebuttal of such nonsense is just plain silly. I'm getting told to "expand" on a rebuttal of something that I'd be expected to expand upon if it where I who said it. If you really, honestly look at what Torvalds said you'd see that he didn't actually say anything.

Just because God says it doesn't mean it makes any sense or should be taken any more seriously than if some random bozo said it. Truth be told, God is just another random bozo.


Responding to the actual question:

Probably the worst, and most common, bad C++ practice is to treat it like C. Continued use of C API functions like printf, gets (also considered bad in C), strtok, etc... not only fail to leverage the power provided by the tighter type system, they inevitably lead to further complications when trying to interact with "real" C++ code. So basically, do exactly the opposite of what Torvalds is advising.

Learn to leverage the STL and Boost to gain further compile-time detection of bugs and to make your life easier in other, general ways (the boost tokenizer for example is both type-safe AND a better interface). It is true that you'll have to learn how to read template errors, which is daunting at first, but (in my experience anyway) it's frankly much easier than trying to debug something that generates undefined behavior during runtime, which the C API makes quite easy to do.

Not to say that C is not as good. I of course like C++ better. C programmers like C better. There are trade-offs and subjective likes at play. There's also a lot of misinformation and FUD floating around. I would say that there is more misinformation floating around about C++ but I'm biased in this regard. For example, the "bloat" and "performance" problems C++ supposedly has aren't actually major issues most of the time and are certainly blown out of the proportions of reality.

As to the issues your professor is referring to, these are not unique to C++. In OOP (and in generic programming) you want to prefer composition over inheritance. Inheritance is the strongest possible coupling relationship that exists in all OO languages. C++ adds one more that is stronger, friendship. Polymorphic inheritance should be used to represent abstractions and "is-a" relationships, it should never be used for reuse. This is the second largest mistake you can make in C++, and it's a pretty big one, but it's far from unique to the language. You can create overly complex inheritance relationships in C# or Java too, and they'll have exactly the same problems.

2
  • 1
    Ironic, as until well after 2007, git only ran portably versions of Linux. Well, any system that was a unix-alike. Then again, given the circumstances that led to the creation of git, I'm certainly not holding it against him.
    – Chris K
    Commented Feb 7, 2011 at 21:47
  • 9
    Linus has a hard time finding any good C++ programmers who want to work for him. Wonder why? I think this is just a chicken-and-egg type of problem.
    – Bo Persson
    Commented Feb 27, 2011 at 13:52
19

I've always thought the dangers of C++ were highly exaggerated by inexperienced, C with Classes programmers.

Yes, C++ is harder to pick up than something like Java, but if you program using modern techniques it's pretty easy to write robust programs. I honestly don't have that much more difficult of a time programming in C++ than I do in languages like Java, and I often find myself missing certain C++ abstractions like templates and RAII when I design in other languages.

That said, even after years of programming in C++, every now and then I'll make a really stupid mistake which wouldn't be possible in a higher-level language. One common pitfall in C++ is ignoring object lifetime: in Java and C# you generally don't have to care about object lifetime*, because all objects exist on the heap and they're managed for you by a magical garbage collector.

Now, in modern C++, usually you don't need to care much about object life-time either. You have destructors and smart pointers which manage the lifetime of objects for you. 99% of the time, this works wonderfully. But every now and then, you'll get screwed by a dangling pointer (or reference.) For example, just recently I had an object (let's call it Foo) which contained an internal reference variable to another object (let's call it Bar). At one point, I stupidly arranged things so that Bar went out of scope before Foo, yet Foo's destructor ended up calling a member function of Bar. Needless to say, things didn't turn out well.

Now, I can't really blame C++ for this. It was my own bad design, but the point is this sort of thing wouldn't happen in a higher-level, managed language. Even with smart pointers and the like, you sometimes still need to have an awareness of object life-time.


*If the resource being managed is memory, that is.

3
  • 9
    Never really have to care about object lifetime in Java and C#? Their GC takes care of memory, but that's only a small part of RAII for me; look at the various "disposable" interfaces those languages have, for example.
    – Fred Nurk
    Commented Feb 7, 2011 at 22:12
  • Having to care about object lifetime would be rare in Java except for the inconvenient design of its I/O library.
    – dan04
    Commented Feb 8, 2011 at 1:46
  • Your dangling reference issue is something I'm interested in trying to solve. I started a discussion on my blog about the direction I'm headed to solve it (pointer promises). Basically I think the language could use a few more smart pointers. Take part in that discussion if you're interested. Nobody else has been so whatever...but if it's something you'd like to see solved... I actually run into the issue a lot more than 10% of the time. Commented Feb 27, 2011 at 21:16
13

The difference in the code is usually more related to the programmer than the language. In particular, a good C++ programmer and a C programmer will both come to similarly good (even if different) solutions. Now, C is a simpler language (as a language) and that means that there are less abstractions and more visibility into what the code actually does.

A part of his rant (he is known for his rants against C++) is based on the fact that more people will take on C++, and write code without actually understanding what some of the abstractions hide and make wrong assumptions.

11
  • 3
    What is the cost of iterating over a std::vector<bool> changing each value? for ( std::vector<bool>::iterator it = v.begin(), end = v.end(); it != end; ++it ) { *it = !*it; }? What is abstracted away in *it = !*it;? Commented Feb 7, 2011 at 20:45
  • 2
    Though it may be unfair to pick on specific language abominations widely criticized as mistakes...
    – Fred Nurk
    Commented Feb 7, 2011 at 20:54
  • 2
    @Fred Nurk: std::vector<bool> is a well known mistake, but it is a really good example of what is being discussed: abstractions are good, but you have to be careful of what they hide. The same can and will happen in user code. For starters, I have seen both in C++ and Java people using exceptions to perform flow control, and code that looks like a nesting function call that is actually a bailout exception launcher: void endOperation(); implemented as throw EndOperation;. A good programmer will avoid those surprising constructs, but the fact is that you can find them. Commented Feb 7, 2011 at 21:00
  • 5
    One of the points of Torvalds is that: he can drive beginners away just by choosing C over C++ (there seems to be more C++ beginners) and C++ being more complex has a steeper learning curve and there are more chances of tripping in a corner case. Commented Feb 7, 2011 at 21:03
  • 2
    +1, this is exactly what Linus is complaining about. He comes off as being anti-C++, but that's not really so. He's only anti-C++-programmer.
    – greyfade
    Commented Feb 7, 2011 at 22:26
13

Overuse of try/catch blocks.

File file("some.txt");
try
{
  /**/

  file.close();
}
catch(std::exception const& e)
{
  file.close();
}

This usually stems from languages like Java and people will argue that C++ lacks a finalize clause.

But this code exhibits two issues:

  • One need to build file before the try/catch, because you cannot actually close a file that doesn't exist in catch. This leads to a "scope leak" in that file is visible after having been closed. You can add a block but... :/
  • If someone comes around and add a return in the midst of the try scope, then the file is not closed (which is why people bitch about the lack of finalize clause)

However, in C++, we have much more efficient ways of dealing with this issue that:

  • Java's finalize
  • C#'s using
  • Go's defer

We have RAII, whose really interesting property is best summed up as SBRM (Scoped Bound Resources Management).

By crafting the class so that its destructor cleans up the resources it owns, we don't put the onus of managing the resource on each and every of its user!

This is the feature I miss in any other language, and probably the one that is most forgotten.

The truth is that there's rarely a need to even write a try/catch block in C++, apart at the top level to avoid termination without logging.

8
  • 1
    I don't think it's the influence of Java as much as it's C. (You could directly substitute fopen and fclose here.) RAII is the "proper" way to do things here, but it's inconvenient for people who want to use C libraries from C++.
    – dan04
    Commented Mar 6, 2011 at 19:12
  • For this type of answer, providing a example of the correct solution, would been appropriate. Commented Jan 6, 2014 at 18:54
  • @ClausJørgensen: Well, the solution unfortunately is not really "showy" since it involves just File file("some.txt"); and that's it (no open, no close, no try...) Commented Jan 7, 2014 at 7:07
  • D also has RAII
    – Demi
    Commented Mar 25, 2014 at 20:50
  • @Demetri: I am not too familiar with D, could you explain how RAII interacts with Garbage Collection ? I know that in Python you can write a "deinit" method, however the documentation warns that in case of cycle of references some objects will not see their deinit method called. Commented Mar 26, 2014 at 7:17
9

One common error that fits your criteria is not understanding how copy constructors work when dealing with allocated memory in your class. I've lost count of the amount of time I've spent fixing crashes or memory leaks because a 'noob' put their objects into a map or vector and didn't write copy constructors and destructors properly.

Unfortunately C++ is full of 'hidden' gotchas like this. But complaining about it is like complaining you went to France and couldn't understand what people were saying. If you are going to go there, learn the language.

3
  • 1
    I think the problem with C++ is that is very easy to shoot yourself in the foot. Sure, there are good C++ programmers around, lots of good software written in C++. But is very difficult to become a good C++ developer. Scott Meyers 'Efficient C++' series shows how many subtleties the language has. Commented Feb 7, 2011 at 22:00
  • I agree. Part of the problem though is that a lot (the majority) of C++ programmers think they know what they are doing when they clearly don't. Did you mean "Effective C++"?
    – Henry
    Commented Feb 8, 2011 at 3:37
  • At least this is getting better with the new rather restrictive rules on implicit generation of copy/move operations in C++0x. In many of the rule-of-three violating cases the implicit generation of copy operations is going to be deprecated and should produce a warning.
    – sellibitze
    Commented Feb 10, 2011 at 16:13
6

C++ allows a great variety of features and programming styles, but that doesn't mean these are actually good ways for C++ to be used. And in fact, it's incredibly easy to use C++ incorrectly.

It has to be learnt and understood properly, just learning by doing (or using it like one would use some other language) will lead to inefficent and error-prone code.

5

Well... For starters you can read the C++ FAQ Lite

Then, several people have built careers writing books about the intricacies of C++:

Herb Sutter and Scott Meyers namely.

As for Torvalds' rant lacking substance... come on people, seriously: No other language out there has had so much ink spilled on dealing with the nuances of the language. Your Python & Ruby & Java books all focus on writing applications... your C++ books focus on silly language features/tips/traps.

2
  • 1
    Hmm...javapuzzlers.com, jimbrooks.org/web/python/#Pitfalls. I'd say Accelerated C++ (for one example) focuses much more on how to write code than these do... Commented Feb 10, 2011 at 20:46
  • 1
    You've brought up a couple of examples of resources that point out edge cases in their respective languages; things that look strange and you're not quite sure how they'd work (although the python list stuff is close)... C++ has an entire industry pointing out things that look perfectly valid that behave in ways you do not expect.
    – red-dirt
    Commented Feb 12, 2011 at 10:23
3

Too heavy templating may not result in bugs at first. As time goes on, though, people will need to modify that code, and they will have a hard time understanding a huge template. That's when bugs enter - misunderstanding causes "It compiles and runs" comments, which often lead to almost-but-not-quite-correct code.

Generally if I see myself doing a three-level deep generic template, I stop and think how it could be reduced to one. Often the problem is solved by extracting functions or classes.

1
  • 8
    Maintaining complicated code in the face of changing requirements always causes bugs without a lot of effort, nothing particularly special about templates there.
    – Fred Nurk
    Commented Feb 7, 2011 at 20:19
2

Warning: this isn't nearly as much of an answer as a critique of the talk that "user unkown" linked to in his answer.

His first main point is the (supposedly) "ever changing standard". In reality, the examples he gives all relate to changes in C++ before there was a standard. Since 1998 (when the first C++ standard was finalized) changes to the language have been quite minimal -- in fact, many would argue that the real problem is that more changes should have been made. I'm reasonably certain that all code that conformed with the original C++ standard still conforms with the current standard. Though it's somewhat less certain, unless something changes quickly (and quite unexpectedly) the same will be pretty much true with the upcoming C++ standard as well (theoretically, all code that used export will break, but virtually none exists; from a practical viewpoint it's not an issue). I can think of few other languages, OSes (or much of anything else computer related) that can make any such claim.

He then goes into "ever changing styles". Again, most of his points are pretty close to nonsense. He tries to characterize for (int i=0; i<n;i++) as "old and busted" and for (int i(0); i!=n;++i) "new hotness". The reality is that while there are types for which such changes could make sense, for int, it makes no difference -- and even when you could gain something, it's rarely necessary for writing good or correct code. Even at very best, he's making a mountain out of a molehill.

His next claim is that C++ is "optimizing in the wrong direction" -- specifically, that while he admits that using good libraries is easy, he claims that C++ "makes writing good libraries almost impossible." Here, I believe is one of his most fundamental mistakes. In reality, writing good libraries for almost any language is extremely difficult. At a bare minimum, writing a good library requires understanding some problem domain so well that your code works for a multitude of possible applications in (or related to) that domain. Most of what C++ really does is "raise the bar" -- after seeing how much better a library can be, people are rarely willing to go back to writing the sort of dreck they would have otherwise. He also ignores the fact that a few really good coders write quite a few libraries, that can then be used (easily, as he admits) by "the rest of us". This really is a case where "that's not a bug, it's a feature."

I won't try to hit every point in order (that would take pages), but skip directly to his closing point. He quotes Bjarne as saying: "whole program optimization can be used to eliminate unused virtual function tables and RTTI data. Such analysis is particularly suitable for relatively small programs that do not use dynamic linking."

He critiques this by making an unsupported claim that "This is a really hard problem", even going so far as comparing it to the halting problem. In reality, it's nothing of the sort -- in fact, the linker included with Zortech C++ (pretty much the first C++ compiler for MS-DOS, back in the 1980's) did this. It's true that it's difficult to be certain that every bit of possibly-extraneous data has been eliminated, but still entirely reasonable to do a pretty fair job.

Regardless of that, however, the much more important point is that this is utterly irrelevant for most programmers in any case. As those of us who've disassembled quite a bit of code know, unless you write assembly language with no libraries at all, your executables almost certainly contain a fair amount of "stuff" (both code and data, in typical cases) that you probably don't even know about, not to mention ever actually using. For most people, most of the time, it just doesn't matter -- unless you're developing for the tiniest embedded systems, that extra storage consumption is simply irrelevant.

In the end, it's true that this rant does have a little more substance than Linus's idiocy -- but that's giving it exactly the damning with faint praise that it deserves.

1

As a C programmer who had to code in C++ due to unavoidable circumstances, here is my experience. There are very few thing I use that is C++ and mostly stick to C. The main reason is because I don't understand C++ all that well. I did/do not have a mentor to show me the intricacies of C++ and how to write good code in it. And without guidance from a very very good C++ code, it is extremely difficult to write good code in C++. IMHO this is the biggest drawback of C++ because good C++ coders willing to handhold beginners are hard to come by.

Some of the performance hits I have seen usually are due to the magical memory allocation of STL (yes, you can change the allocator, but who does that when he starts out with C++?). You usually hear arguments by C++ experts that vectors and arrays offer similar performance, because vectors use arrays internally and the abstraction is super efficient. I have found this to be true in practice for vector access and modifying existing values. But not true for adding a new entry, construction and destruction of vectors. gprof showed that cumulatively 25% of time for an application was spent in vector constructors, destructors, memmove (for relocation of entire vector for adding new element) and other overloaded vector operators (like ++).

In the same application, vector of somethingSmall was used to represent a somethingBig. There was no need for random access of somethingSmall in somethingBig. Still a vector was used instead of a list. The reason why vector was used? Because the original coder was familiar with array like syntax of vectors and not very familiar with iterators needed for lists (yes he is from a C background). Goes on to prove that a lot of guidance from experts is required to get C++ right. C offers so little basic constructs with absolutely no abstraction, that you can get it right much easier than C++.

0

Although I like Linus Thorvalds, this rant is without substance - just a rant.

If you like to see a substanted rant, here is one: "Why C++ is bad for the environment, causes global warming and kills puppies" http://chaosradio.ccc.de/camp2007_m4v_1951.html Additional material: http://www.fefe.de/c++/

An entertaining talk, imho

0

STL and boost are portable, at the source code level. I guess what Linus is talking about is that C++ lacks an ABI (application binary interface). So you need to compile all libraries you link with, with the same compiler version and with the same switches, or else limit yourself to the C ABI at the dll boundraries. I also find that annyoing.. but unless you are making 3rd party libraries, you should be able to take control over your build environment. I find restricting myself to the C ABI is not worth the trouble. The convenience of being able to pass strings, vectors, and smart pointers from one dll to another is worth the trouble of having to rebuilding all libraries when upgrading compilers or changing compiler switches. The golden rules I follow are:

-Inherit to reuse interface, not implementation

-Prefer aggregation over inheritance

-Prefer where possible free functions to member methods

-Always use the RAII idiom to make your code strongly exception safe. Avoid try catch.

-Use smart pointers, avoid naked (unowned) pointers

-Prefer value semantics to reference semantics

-Don't reinvent the wheel, use stl and boost

-Use the Pimpl idiom to hide private and/or to provide a compiler firewall

-6

Not putting a final ; at the end of a clase declaration, at least in some versions of VC.

3
  • 4
    This is perhaps a very common mistake for beginners (as is almost anything for someone still learning the basic syntax), but are there many who would call themselves competent and still find this mistake to be noteworthy?
    – Fred Nurk
    Commented Feb 7, 2011 at 22:14
  • 1
    Wrote it just because the compiler gave you an error that had nothing to do with the lack of a semicolon. Commented Feb 7, 2011 at 22:18
  • 2
    yeah, the exact same error is what you get from a C compiler. Commented Feb 11, 2011 at 15:21

Not the answer you're looking for? Browse other questions tagged or ask your own question.