The C and C++ Trivia
If you're anything like me, when you run into a little programming problem you just trot down to the library and pick up a couple of books/manuals on whatever it is you need to master. Do a little reading and POW! You've got the tools you need.
When I ran into problems with my first major C++ project, however, I discovered that each new "solution" just slammed me into another roadblock. I needed answers. But all I got was frustration.
And it looks like I'm not the only one. According to the 2008 Embedded Market Study, C++ use has lagged behind C and the numbers have not significantly improved. In 2005, only 26% of those surveyed used C++ (compared to 51% with C) and while C use rose about six percentage points over the next three years, C++ use fluctuated before it settled into a mere three percent increase.
No surprise, given the frustration factor, but the fact of the matter is C++ is an amazingly useful tool in embedded design. But before you can use its awesome powers, you just have to wrap your mind around one little detail: C++ isn't a programming language; it's a worldwide research project running about in disguise.
Here's the lowdown. Reading compiler manuals will only get you so far. The bulk of C++ is documented in papers, presentations, and (most importantly) the experience of its users. In fact, the range of legal C++ code is so wide that you have to write idiomatically to be understood by other programmers (see References [2, 3, 4, 5]). You want to be good? Then you've got to get involved.
So, come on. Let's dive in and get our hands dirty.
Many of us remember when the number one (frustrating) feature of C++ was that its portable code wasn't actually portable. These days, C++ is much more portable on desktop and embedded platforms, but portability still isn't a given .
Your best bet? You got it – head to the library! C++ library authors and vendors are C++'s QC department. To see how your compiler measures up, start with the Boost library regression test summary  and the Dinkumware standard-library manual .
For many platforms , GNU GCC is a good bet for a conforming compiler with a price that just can't be beat. If there's no C++ compiler for your platform, try Comeau C++ . It claims to track the standard very closely, and compiles C++ to C for your favorite toolchain. While I haven't tried it myself, the reviews are good.
The compiler's main business is managing names, not generating assembly. The code int i; doesn't necessarily allocate space for an integer. Instead it gives the name i a new property: it can refer to something of int type in the current scope.
This matters when you use overloading and templates, when what a name refers to depends on the context in which it is used. Template matching and overload resolution are designed to make the common cases Just Work, but this can lead to confusing errors about ambiguous function calls (Appendix B in ).
Partial specialization, where a template can behave differently for subgroups of its arguments, adds even more power with its attendant complexity (Sections 1.5 and 2.2 in ). Unfortunately, there's no shortcut here, so start simply and learn the rules as you go
The happy news if you're using a good library (whether yours or someone else's), is that C++ is pretty close to "Do What I Mean." However, coding libraries is very different from coding applications. Take a look at your compiler's Standard Template Library (STL) implementation. Library code is some of the densest, gnarliest code there is. In fact, most of the fanciest features in C++ are intended only for writing libraries (Page xiii in ).
Now, you might think that all these fancy features would make for bloated libraries; bit before you start worrying, remember that C++ libraries are generally designed to optimize the most common case.
While you do have to be careful, using a library doesn't automatically cause bloat and slowdown. For example, inlining is the compiler's job"not the library's"so you control code size with compiler options. As always, the trade-off is code size versus memory or maximum stack depth.
At its core C++ is a multiparadigm language. To get the best solution to a problem, you generally combine elements of object-oriented, procedural, and (sometimes) functional languages.
The great part is that it's designed around the zero-overhead rule; you don't pay in space or time for any feature you don't use . Consequently, C++ supports lots of different styles that you can mix and match.
Once again it must be said that the most challenging stumbling blocks of C++ are also some of its best features. Take friend functions (the standalone functions that have access to the implementation details of a class).
At first, I thought they damaged a clean object-oriented design by introducing misplaced procedural code. As it turns out, however, when properly used, friends are not an import into a class, they are part of the class that happens to be called as func(obj) instead of obj.func().
Friend functions give you the freedom of more a natural syntax (Pages 217-218 in ). Sutter calls this the "Interface Principle": "all functions, including free functions, that both 'mention' [a class] X [and] are 'supplied with' X are logically part of X" (Page 123 in ).
The use of friends highlights an important general point about classes: in C++, classes are primarily for encapsulation, not inheritance. Inheritance is a special-purpose tool. When writing a class, first ask how to make it look like part of the language, rather than how to inherit from it.
You can use the Rule of Three: copy constructors, assignment operators, and destructors go together (Section 11.3.6 in ). A class with "value semantics" (one that behaves like an integer) is free of lots of potential allocation and parameter-passing bugs.
Up until recently, I saw the compiler as just another tool " useful but not exactly world-changing. Yet in C++, a compiler cannot only do tremendous amounts of compile-time processing, it can check at compile-time things that once had to be checked at run-time!
It's like a second shell. Class templates and specializations form a functional language of their own that you can use to do integer calculations, and even manipulate types at compile-time . The boost library even does amazing things with the preprocessor. Sure, you have to rely more heavily on your compiler's optimizer, but you can design on a much higher level than you ever thought possible.
Don't look at C++ as a just long line of frustrations"get yourself outside the box and see the possibilities. C++ excels at large-system design. You can reuse code on a very high level, and still get the low-level control you need for an embedded system.
While you can bet that I'm not going to try C++ on a PIC anytime soon. But as embedded devices grow in their complexity, there's more and more reason to dive in and see what C++ can do for you, as the two pages of idiom examples that follow illustrate.
Listing 1 below shows one of the most common and useful C++ idioms: Resource Acquisition Is Initialization (RAII). If the constructor of an object (Lock::Lock) acquires a resource (mutex) and the destructor (Lock::~Lock) releases that resource, the compiler guarantees you'll never leave the relevant code region without releasing that resource. This eliminates many concurrency errors and leaks.
|Listing 1 – RAII: resource acquisition is initialization|
RAII really shines with exception handling. Exceptions separate error-handling logic from program logic, but doing it right is tedious. Listing 2a below shows a program that does memory allocation and handles std::bad_alloc exceptions, which could happen any time you try to allocate heap.
|Listing 2a – Exception handling, hardcoded|
Listing 2b below, using helper class IntHolder (Listing 2c below), is much simpler. IntHolder is an example of a handle class (Chapter 14 in ), a safe wrapper class written to hold an object that requires careful handling.
|Listing 2b – Exception handling, RAII: application code|
|Listing 2c -Exception handling, RAII: library code|
Caution: if a program (or thread) terminates because of an unhandled exception, all bets are off. On Microsoft Visual C++ 6.0, the program immediately terminates without doing any cleanup at all.
On GCC 3.4.4, it dumps core. To prevent this, make the main function of any application or thread like Listing 2d below use a catch(…) to grab all unhandled exceptions. Even if the catch body is a no-op, the catch will make sure the exception handlers run as they should.
Listing 2d – Preventing exceptions from escaping
Another useful C++ idiom is the pimpl (Pages 99-118 in ). In C++, the private declarations of a class are visible in the header file for that class. That means information that should be private is really public, and that you have to recompile anything that uses that class whenever you tweak what's under the hood.
|Listing 3a – pimpl.h|
One solution is to move your private implementation into a separate class and store only a pointer to that class: a Pointer to IMPLementation. Listing 3a above shows the header file, and Listing 3b below the implementation, for a simple class with a pimpl. File pimpl.h declares Interface::Implementation to be a class, but gives no details.
|Listing 3b – pimpl.cpp|
File pimpl.cpp provides the definition of Implementation and whatever of the definition of Interface wasn't in pimpl.h. Interface allocates and deallocates an Implementation using RAII. then uses Interface::Frob()Implementation::Frob() to do the work.
The space overhead of a pimpl is an extra object on the heap, typically from four to 32 bytes (Page 78 in ). The time overhead is that every call turns into two: one into the interface and one into the implementation. For large systems, it's worth paying the price.
A final caution: Always include a default constructor in the interface classes (Interface::Interface()). If you don't, the compiler will generate one that wants the details of Implementation. It won't find the details, so compilation will fail.
The Original Author of this Article is Chris White, an embedded-systems designer and intellectual-property manager working on organic LED displays for Eastman Kodak Company in Rochester, NY. He can be contacted by email at [email protected].
The Annotated Bibliography (Read First Resources)
1. Koenig, Andrew, and Moo, Barbara E. Accelerated C++: Practical programming by example. Boston: Addison-Wesley, 2000. (Author's note: Read this first; it introduces C++ [from zero through templates, user-defined conversions and polymorphism] in modern-C++ style.)
2. Sutter, Herb. Exceptional C++: 47 engineering puzzles, programming problems, and solutions. Boston: Addison-Wesley, 1999.
3. Sutter, Herb. More Exceptional C++: 40 new engineering puzzles, programming problems, and solutions. Boston: Addison-Wesley, 2002.
(Author's Note: Read the two Exceptional C++ books between Accelerated C++ above and Modern C++ referenced below. These are essentially idiom catalogs that will help you write more robust, maintainable programs. They are very readable and very informative. See also Herb Sutter's GotW.ca Home Page.)
4. Cline, Marshall. "C++ FAQ Lite – Frequently Asked Questions," . (Author's note: The first place to look online.)
5. Gamma, Erich, Helm, Richard, Johnson, Ralph, and Vlissides, John. Design Patterns: elements of reusable object-oriented software. Reading, Massachusetts: Addison-Wesley, 1995. The Gang of Four (GoF) catalog of design patterns. (Author's Note: Read this once you're comfortable with inheritance and polymorphism. Includes an example or two for each pattern, but there's no substitute for practice.)
6. Alexandrescu, Andrei. Modern C++ Design: Generic programming and design patterns applied. Boston: Addison-Wesley, 2001. (Author's Note: Has to be seen to be believed. Read this either when you're fluent in inheritance and templates and want to combine the two, or when you want to know why you should become fluent.
(Other useful C++ Resources)
7. Becker, Thomas. "On the Tension Between Object-Oriented and Generic Programming in C++ and What Type Erasure Can Do About It," C++ Source. (Author's Note: I had to ponder over a lot of those awful template error messages until I had my code working on each of the three compilers that I currently support.)
8.Boost Project. "Testing," . .
9. Comeau C++ homepage. .
10. Dinkumware Ltd. "Dinkum Compleat Libraries: Introduction" .
11. GNU Project. "Hardware Models and Configurations." The targets supported by gcc.
12. Stroustrup, Bjarne. "Evolving a language in and for the real world: C++ 1991-2006."