C++ vs. C

From Pin Eight
Jump to: navigation, search

This is a mini-rant, a short essay refuting a common misconception among users of an Internet forum. If you think this essay is FUD, feel free to explain why on the essay's talk page.

The C++ programming language brings improvements over its predecessor C, but fans of other languages point out that C++ is still not perfect. Over a decade after the 1998 standardization of the C++ programming language, the C++ vs. C debate continues.

Contents

Pluses

Compared to C, C++ has a bunch of new language features with little or no runtime overhead because they are translated to code equally as efficient as the equivalent C code:

  • namespaces
  • function overloading
  • type-safe new(std::nothrow)
  • STL, the part of the C++ standard library with containers and algorithms
  • non-virtual methods
  • references, needed by operator overloading

A few features are as efficient as C yet still rawther deceptive and easy to misuse because the "simple" syntax hides how much code is actually being generated:

  • operator overloading, when compared to ordinary function-call syntax
  • virtual (polymorphic) methods, when compared to C function pointer tables, especially the pure virtual method's undefined behavior that some compilers may implement as an exception
  • templates instantiated several times, when compared to using the preprocessor to instantiate multiple copies

PROTIP: A class is a struct whose default access is private. That's the only difference. If you specify access before any members, class and struct mean the same thing.

Minuses

C++ also has some features requiring possibly expensive runtime library support:

  • throw
  • the default version of new without std::nothrow, which throws std::bad_alloc
  • <iostream>

Templates

Templates have a couple drawbacks:

  • Type names in error message become far more difficult to interpret. Common implementations may expand a template type name fully in diagnostic messages even if the source code accesses the type through a typedef. For example, an error message involving the std::string type is likely to provoke this reaction: "basic_string? I'm using C++, not BASIC! And what the fsck is char_traits?" (True, implementations are not the language, but a language is only as good as its best free implementation.)
  • Programmers can lose track of for how many different type combinations they have instantiated a template, causing code size to balloon. There is a new feature in C++11 called extern template allowing for explicit instantiation, but not all compilers support it yet.

Exceptions

Exceptions (throw) also have a few drawbacks:

  • Some platforms require different stack frame formats for code that uses exceptions and code that does not use exceptions. Unless you completely disable exception support using compiler flags, the exception support may still eat up a few percent of CPU time, roughly 8 to 10 percent in GCC.[1]
  • Even if you have determined that a particular compiler imposes little to no runtime speed penalty for exceptions, the size of the required library support might cause a problem on embedded or handheld devices with less than about a megabyte of RAM. I'll grant that this has become somewhat less important in the 2010s as the market has transitioned from handheld devices with 384 KiB of RAM, such as a Game Boy Advance, to smartphones with a thousand times that much memory.
  • A practical counterpart to the finally keyword of Java and Python was not added until C++11. True, there isn't as much need for finally in C++ as in languages that rely on a garbage collector, given the idiom of allocating resources in constructors that C++'s deterministic destruction allows. But a method often still needs to restore the object's fields to a consistent state before eating or rethrowing the exception. Once compilers implement C++11, however, the programmer can build a scope-guard with std::shared_ptr and lambda expressions.
  • If a function throws exceptions, all callers have to be aware of all exception types that it may throw, and all callers have to be written in C++ using exception style so that they can catch the exceptions. And unlike Java with its checked exceptions, C++ compilers offer no built-in automated tests to ensure that a method either handles or rethrows exceptions that its callees throw. This is one reason why Google is not a fan of C++ exceptions.[1]

On platforms without virtual memory, a program must be aware of possible out-of-memory conditions. The STL allows passing an allocator to all containers, but most implementations appear to exhibit undefined behavior when allocate() returns 0 like new(std::nothrow) does. I've been told one STL implementation can be built with nothrow in mind: STLPort.[2][3] EA Games created an out-of-memory-aware allocator.

iostream

The <iostream> library is another divisive issue. It was envisioned as a type-safe alternative to <cstdio>, but implementations are hairy, convoluted, bloated, and inefficient. This goes double if you have to use a statically linked implementation of the C++ standard library, either because the operating system provides no C++ standard library (e.g. handheld video game systems) or because your compiler's C++ ABI differs from that of the operating system publisher's own development tools (e.g. MinGW). Hello World programs with -Os and statically linked libstdc++ in one version of MinGW resulted in 5,632 bytes for <cstdio> but 266,240 bytes for <iostream>. A devkitARM project targeting Game Boy Advance had similar results: 5,156 bytes for C-style I/O and 253,652 bytes for <iostream>. (This is expected; Thumb and x86 have comparable code density.) Even removing some unreachable code with -Wl,-gc-sections couldn't get the GBA project below 180,032 bytes. (For comparison, the GBA's main RAM is 262,144 bytes.) These tests are with GNU libstdc++, which initializes date, time, and money aspects of a locale for each stream even if the program never shifts a date, time, or money object into the stream. This is because it was conceived before templates and instead uses virtual inheritance and "facets", which C++ implementations can't optimize well.[4] Some third-party C++ standard library implementations such as uClibc++ are designed for space efficiency and leave out features such as locale support that aren't as useful in small-memory systems. In fact, one C++ advocate claims that comparing memory usage of C++ to C by targeting a sub-megabyte platform with GNU libstdc++ sets up a huge straw man.[5]

Yet some C++ fanboys claim that anything using good old <cstring> and <cstdio> instead of new-fangled <string> and <iostream> isn't in the spirit of C++, whatever that means. They cling to item 2 in the second edition of Scott Meyers' Effective C++, which promotes <iostream> over <cstdio>, and ignore item 23 of his sequel ("consider alternative libraries"). It appears that Meyers eventually recognized that <iostream> is imperfect and removed item 2 from the third edition. Someone who knows a few people on the standards committee thinks Boost.Format is idiomatic and <iostream> is just idiotic.

"As an embedded programmer, I shun C++." -- Arlet, 2011-11-01

References

  1. Andrew Binstock. "The Scourge of Error Handling". Dr. Dobb's, 2012-12-05. Accessed 2012-12-08.

External links

Personal tools