Phil McLaughlin founded Hybrid Technical Services in 1983. He currently does consulting and custom development "mostly" for Windows. He can be reached at Compuserve [73171,105] or at PhilMcL@AOL.COM.
Introduction
Hundreds of books on the market now cover the C and C++ languages. Some of these books claim to teach the beginner how to write code in so many days or less. By contrast, some massive references target the programming expert and are thus packed with finely granulated data. The middle ground between these extremes is still sparsely populated. Scott Meyers' offering in the intermediate category, Effective C++, is organized a little differently from most.Effective C++ is built around 50 rules or guidelines: "50 Specific Ways to Improve Your Programs and Designs." These pieces of advice concentrate primarily on the aspects of C++ that distinguish it from ordinary C. The book covers classes, templates, inheritance, virtual functions, etc., as you might expect from any book on C++ but it also provides more general advice, such as in Item 1: "Use const and inline instead of #define." This collection of 50 essays, each illuminating a design situation in C++, is both readable and thought provoking, but you might not want to read it on a dark stormy night.
Rules for C++ Programmers
Here are just a few examples from Meyers' Rules:
The short discussions accompanying these rules assume prior acquaintance with C++ programming. You wouldn't go to this book to find out how to set up inheritance or how to use a template. Also, Meyers' book is not a tutorial on syntax, although a beginner might find it useful to study his examples. Meyers primarily lends insight on C++ design issues, such as when to set up inheritable member functions vs. when to use a template, or when to use neither.
- Use new and delete instead of malloc and free.
- Check the return value of new.
- Avoid hiding the global new.
- List members in an initialization list in the order in which they are declared.
- Make destructors virtual in base classes.
- Use const whenever possible.
- Avoid overloading on a pointer and a numerical type.
- Never return a reference to a local object or a dereferenced pointer initialized by new within the function.
- Make sure public inheritance models the "is-a" relationship.
- Never redefine an inherited nonvirtual function.
- Prefer compile-time and link-time errors to run-time errors.
- Read the Annotated Reference Manual.
Derivation or Template?
For example, the intermediate-level program designer knows how to create an inheritable class. The designer also knows how to use a template. Which approach is called for in a given situation?Item 42, "Differentiate between inheritance and templates," illuminates this issue by considering two class specifications that are similar in some respects but require radically different implementations. The first specification calls for stacks of simple data objects, such as ints, floats, or complex numbers. The second specification calls for a collection of cats.
Meyers shows that the difference in implementing such classes involves the realtionship between each class's behavior and the type of object being manipulated. The ints, floats, or complex numbers all behave identically as they are pushed on and popped from the stack, but Meyers's cats behave in ways specific to their breed apparently a Tabby does not behave like a Persian or a Manx, as it comes out of storage. Since the type of cat affects the behavior of the class, inheritance is called for.
So the designer should ask, "Does the type of the object affect the behavior of the class?" Only when the type has no effect is the template appropriate.
The Virtues of Constness
Item 21 introduces six pages of what Meyers calls "a distressingly complete discussion of the meanings and uses of const." First for the meanings. Some of us might stumble over the meanings of these 4 identifiers:
char *GREETING = "Hello"; const char *GREETING = "Hello"; char * const GREETING = "Hello"; const char * const GREETING = "Hello";Meyers provides a rule of thumb: If const appears to the left of the asterisk, what's pointed to is constant. If const appears to the right, the pointer itself is constant. If const shows on both sides, as in the fourth line of code, then both the pointer and what it points to are constant.All programmers use const when the compiler forces them to do so, and most will use constants when the merits are obvious. According to Meyers, that attitude is far too passive. Thus Item 21 is entitled "Use const whenever possible."
Several benefits accrue. For example, functions whose declarations are identical except for "constness" can be overloaded. More important, declaring const prevents things from changing that shouldn't. It may stop some other part of the program from un-anchoring a pointer, or prevent a strange program from blowing away a reference.
This rule fits in with one of the book's general themes, which is to make the compiler do the work for you. Meyers starts early on this theme with Item 1 "Use const and inline instead of #define."
Making the Compiler Do the Heavy Lifting
What distinguishes between the following two statements?
#define COR_FACTOR 32.3607 const float COR_FACTOR = 32.3607;The #define works by text substitution prior to compile time, but the const reserves storage for a float at compile time. In the #define statement, the preprocessor substitutes the text of the numeric value into the source code wherever it finds the identifier. This substitution process happens before the compiler sees the code. Therefore, if the defined constant generates an error, the compiler's error message will refer to the value 32.3607 rather than the mnenomic COR_FACTOR, as it would if the identifier had been defined with the const statement.At first glance, it may not be obvious how to convert the following #define statement to a const, because it involves a character string:
#define GREETING "Hello"However, the "double constness" of the fourth example above is tailor-made for this substitution:
const char * const GREETING = "Hello";For an explanation of why double constness is required, and for more reasons not to use #define, read Item 1 in Meyers's book.
Virtual or Non-Virtual?
We have all been taught from an early age that calls to virtual member functions carry an overhead. But what are the costs of leaving member functions nonvirtual? To get right down to it, why exactly should you declare a function one way or another? This decision is clarified in Item 36: "Differentiate between inheritance of interface and inheritance of implementation."In discussing this item, Meyers points out that the interface, which declares the external form by which the member function is accessed, is always inherited. The designer can only choose whether or not to allow the implementation (the executing code) to be inherited.
Item 37 warns, "Never redefine an inherited nonvirtual function." Meyers explain why, much better than I can in this space. Briefly, what is true of a publicly inherited class should also be true of a descendant (because public inheritance means "is-a." See Item 35). A descendant that redefines the (statically bound) behavior of the ancestor breaks the "is-a" relationship. So with Item 37's restriction on redefinition, it turns out that a nonvirtual function is something of a special case. The nonvirtual member function is "invariant over specialization."
Some of us who were early C++ programmers used virtual only when we knew we had to, on behalf of some descendant class. We thought we were being efficient by avoiding overuse of virtual, but in reality we were throwing obstacles in the way of future sub-classing. A function that is left nonvirtual imposes a complication that prevents descendant functions from safely changing the behavior. A better rule of thumb might be "Always make member functions virtual unless there is a specific reason to constrain the behavior of descendants."
Not for a Dark and Stormy Night
The 50 essays in this book are thought provoking, and a little sobering. This is not a book that fires up the reader to immediately start slapping code together and building class hierarchies without forethought. Most readers of this magazine already know that C or C++ do not protect the programmer quite like some other languages. However, these 50 rules reveal lots of ways for errors to creep in that might not have been considered. The implications are more than a little scary. With the heavy use of indirect addressing, parameter passing by reference, dynamic memory, and run-time linkage, all sorts of things are potentially going bump in the night. Meyers' rules attempt to formulate practical antidotes against sometimes mysterious misbehaviors.Under the heading, "Specific Ways to Improve Your Programs and Designs," the book colorfully illustrates by example lots of subtle wrong moves to avoid in C++. Vampire-like "memory leaks" arise from improper coordination between allocation and de-allocation of dynamic memory. Because memory leaks are not compiler-recognized errors, they slip right out past your debugger into run tune, where they suck the life from your executables. Failing to delete a pointer in a destructor gives rise to "a slowly growing cancer that will eventually devour your address space." If you re-define a nonvirtual function in a descendant class, "objects will likely exhibit schizophrenic behavior." Anytime you write a function, "aliasing can crop up in any number of nefarious disguises" (See Item 17).
Conclusion
Running just barely over 200 pages, this collection might be easy to overlook, particularly in this age when the 800-page tome is typical. That would be a mistake. This book packs a high density of useful information (as opposed to mere data). The book clears up some mysteries that are rarely covered in the large references. I found this volume to be more readable than others in its class, partly due to the division into 50 bite-size items. The short discussions accompanying the rules are often richer and more thought-provoking than their headings indicate. Examples and naming conventions are consistent from one topic to another. The sections are cross referenced, so going after particular interests or skipping around is easy and profitable. Read one or two at lunch time for a few weeks, and become a more aware designer. I heartily recommend Effective C++ to anyone who aspires to mastery of C++ at the intermediate level or above.Title: Effective C++ (50 Specific Ways to Improve Your Programs and Designs)
Author: Scott Meyers
Publisher: Addison-Wesley
Price: $25.75
ISBN: 0-201-56364-9