Monday, October 07, 2013

C++ bollocks

Every now and then I come across what I call "C++ bollocks". What do I mean? I am referring to various statements made about certain aspects of C++ that are, well, complete bollocks. The statements might have been true to some extent in the
dim and distant past, but anyone still trotting out these statements in this day and age is, in my arrogant opinion, talking bollocks. Here are some examples, divided into categories.

all too frequent bollocks

  • C++ iostream are too slow. Use the printf family instead.
    As one person has pointed out via the comments, I do not have a direct refutation for this yet. The FAQ link merely explains why iostreams are generally superior.
  • C++ exceptions are too slow. Use error codes or boolean return status instead.
    see the FAQ. Also see , "Why use exceptions?"
  • Don't use std::endl, it's too slow.
    It is true that std::endl flushes the output buffer, which has a performance hit. This is even mentioned in the FAQ-lite. However, such concerns over micro-efficiency are usually misplaced. Premature optimisation is the root of all evil.
  • C++ strings are too slow. Prefer C char arrays.
    Some people prefer C to C++. Those people should leave C++ alone and work in their preferred language. Also see , "What's wrong with arrays?".
  • avoid virtual functions. The performance hit on virtual dispatch is too great.
    See the FAQ
  • always implement getters and setters as inline. It's faster.
    See the FAQ. As ever, premature optimisation is the root of all evil. People making these claims never have the figures to back them up. Their response to being challenged is "everyone knows this".
  • provide convenient implicit type conversion by implementing cast operators
    Actually you usually want to do the complete opposite of this. This is why many coding guidelines say that single arg ctors must be qualified by explicit. and that cast operators should not be written.
  • Failing to make a base class dtor virtual is no big deal. It will just result in a small memory leak.
    Wrong. It is actually undefined behaviour. See the FAQ and the Jargon file
  • saying delete mem instead of delete []mem (to go with the new type[count]) is no big deal. It will just result in a small memory leak.
    Wrong. It is actually undefined behaviour. See the FAQ and
    of course, the Jargon File
  • Use Yoda notation to guard against accidental assignment. I have a rant about this one.

infrequent bollocks

  • don't throw exceptions from constructors.
    this is from the same school of bollocks as "C++ exceptions are too slow".
    See the FAQ
  • prefer pointers to references since references dont make it clear when the object can be modified.
    See the FAQ and this Parashift article.


9 comments:

Anonymous said...

Is there a reason none of the links are clickable?

Anonymous said...

Well said

Anonymous said...

I feel that it's important to mention that quite a few getters and setters are essentially trivial, and in these cases it's rather obvious that inlining is beneficial (justification: compile up an inlined and non-inlined trivial setter, especially for a primitive type; the inlined one is a simple copy and the non-inlined one is a function call wrapping a simple copy, and on most systems one would expect the latter to take longer than the former and to have more code associated with it). Such trivial getters/setters (indeed, also simple-but-not-trivial ones) are also much easier to write when they're inline, and, since their inline-ness doesn't change the syntax for calling them in user code or their behavior, it's easy to switch to a non-inline implementation if the need arises anyway. Getters and setters should not always be made inline, but they shouldn't never be made inline either, and there are many cases where making them inline makes much more sense.

Anonymous said...

"prefer pointers to references since references dont make it clear when the object can be modified."

This is true and good API design.

myFunc1(variable);
myFunc2(&variable);

When you read this two lines, its clearly for the programmer that myFunc2 is going the modify variable.
Its not the case for myFunc1

Anonymous said...

"prefer pointers to references since references dont make it clear when the object can be modified."

This is true and good API design.

myFunc1(variable);
myFunc2(&variable);

When you read this two lines, its clearly for the programmer that myFunc2 is going the modify variable.
Its not the case for myFunc1

Anonymous said...

Can't see how the link about iostreams debunks the 'bollocks' that they are slow... sure they are type-safe and less error prone, but that doesn't contradict that they are slow

root@boy said...

I couldn't agree more. Every C++ programmer please read those!

Anonymous said...

Hi Andrew,

These issues are rarely black and white, so - FWIW - I'll record my sympathy with a few of the "bollocks" claims...

- iostreams/printf, exceptions/error-codes, strings/arrays inevitably have different performance characteristics - with the second in each pair typically being genuinely if marginally faster in most systems for some usage but requiring considerably more care; in very rare cases in realtime systems it may be worthwhile selecting between them on the basis of actual profiling results

- endl is often a premature pessimisation not even chosen legitimately for the flush functionality; perhaps due to: lack of awareness of tied stream functionality, lack of awareness that cerr tends to be line-buffered anyway, exposure to non-Standard systems that flush too much (so endl wasn't worse) or too little (so it sometimes seemed useful), and possibly a feeling that endl is more communicative (I won't say "self-documenting" because it doesn't document the flush aspect at all) than '\n' or easier to remember (for beginners)

- last time I benchmarked, inlined get/set function on my system were 9 times faster than out-of-line equivalents

- the inline/out-of-line overhead is a legitimate potential reason to avoid virtual dispatch in certain limited circumstances (presumably in favour of inlined switching on an inline accessed "type id" field, though with those couple operations and at least a trivial field access the maximum potential performance benefit will have shrunk to e.g. 2-3x)

- glad to see "explicit operator T" in C++11 :D

- "[no] base class dtor virtual is no big deal" - well, memory leak / UB issue's with "deletion of a derived object via a pointer to a base lacking a virtual destructor"; if (while?) you don't attempt such deletion all's good

- of "delete[]" / "small memory leak" - FWIW, if you "survive" the UB (which might not happen to have observable negative consequences on a particular build/run/platform), you'll likely see consistently that only the first element's destructor runs - that can sometimes be a hint as to the problem.

- prefer pointers to references: taking a step back, ideally the language would support some caller notation for granting the callee read, write or read-write access to the variables being passed - e.g. f(>y, " might mean an input and "<" an output, with the compiler checking that f doesn't read pure outputs before assigning to them (it can't always accurately assess this at compile time but can give valid errors sometimes and optional warnings others), that the caller has assigned to any input variables before the call (ditto), and a caller variable without ">" gets a compiler error if accepted by non-const reference. I wouldn't want to have to use it for every function declaration and call, but would like to have it available when wanted. Without such support, f(&likely_output, input) is the closest achievable without ugly and verbose wrapper objects and casts. The FAQ's "a form of information hiding, which is an asset rather than a liability. E.g., programmers should write code in the language of the problem rather than the language of the machine. " is bollocks; that use of "&" hints at potential callee modification - very much at the problem level, and not a use related to potential by-value / by-reference-to-avoid-a-copy machine-level implications.

Cheers,
Tony Delroy

Andrew Marlow said...

Many thanks for the feedback. I have corrected the links, they should all be clickable now. Other points I will have to get back to later...