Effective C++, 2E | Item 50: Improve your understanding of C++ Back to Item 49: Familiarize yourself with the standard library. Continue to Afterword Item 50: Improve your understanding of C++. There's a lot of stuff in C++. C stuff. Overloading stuff. Object-oriented stuff. Template stuff. Exception stuff. Namespace stuff. Stuff, stuff, stuff! Sometimes it can be overwhelming. How do you make sense of all that stuff? It's not that hard once you understand the design goals that forged C++ into what it is. Foremost amongst those goals are the following: Compatibility with C. Lots and lots of C exists, as do lots and lots of C programmers. C++ takes advantage of and builds on er, I mean it "leverages" that base. Efficiency. Bjarne Stroustrup, the designer and first implementer of C++, knew from the outset that the C programmers he hoped to win over wouldn't look twice if they had to pay a performance penalty for switching languages. As a result, he made sure C++ was competitive with C when it came to efficiency like within 5%. Compatibility with traditional tools and environments. Fancy development environments run here and there, but compilers, linkers, and editors run almost everywhere. C++ is designed to work in environments from mice to mainframes, so it brings along as little baggage as possible. You want to port C++? You port a language and take advantage of existing tools on the target platform. (However, it is often possible to provide a better implementation if, for example, the linker can be modified to address some of the more demanding aspects of inlining and templates.) Applicability to real problems. C++ wasn't designed to be a nice, pure language, good for teaching students how to program, it was designed to be a powerful tool for professional programmers solving real problems in diverse domains. The real world has some rough edges, so it's no surprise there's the occasional scratch marring the finish of the tools on which the pros rely. These goals explain a multitude of language details that might otherwise merely chafe. Why do implicitly-generated copy constructors and assignment operators behave the way they do, especially for pointers (see Items 11 and 45)? Because that's how C copies and assigns structs, and compatibility with C is important. Why aren't destructors automatically virtual (see Item 14), and why must implementation details appear in class definitions (see Item 34)? Because doing otherwise would impose a performance penalty, and efficiency is important. Why can't C++ detect initialization dependencies between non-local static objects (see Item 47)? Because C++ supports separate translation (i.e., the ability to compile source modules separately, then link several object files together to form an executable), relies on existing linkers, and doesn't mandate the existence of program databases. As a result, C++ compilers almost never know everything about an entire program. Finally, why doesn't C++ free programmers from tiresome duties like memory management (see Items 5-10) and low-level pointer manipulations? Because some programmers need those capabilities, and the needs of real programmers are of paramount importance. This barely hints at how the design goals behind C++ shape the behavior of the language. To cover everything would take an entire book, so it's convenient that Stroustrup wrote one. That book is The Design and Evolution of C++ (Addison-Wesley, 1994), sometimes known as simply "D&E." Read it, and you'll see what features were added to C++, in what order, and why. You'll also learn about features that were rejected, and why. You'll even get the inside story on how the dynamic_cast feature (see Items 39 and M2) was considered, rejected, reconsidered, then accepted and why. If you're having trouble making sense of C++, D&E should dispel much of your confusion. The Design and Evolution of C++ offers a wealth of insights into how C++ came to be what it is, but it's nothing like a formal specification for the language. For that you must turn to the international standard for C++, an impressive exercise in formalese running some 700 pages. There you can read such riveting prose as this: A virtual function call uses the default arguments in the declaration of the virtual function determined by the static type of the pointer or reference denoting the object. An overriding function in a derived class does not acquire default arguments from the function it overrides. This paragraph is the basis for Item 38 ("Never redefine an inherited default parameter value"), but I hope my treatment of the topic is somewhat more accessible than the text above. The standard is hardly bedtime reading, but it's your best recourse your standard recourse if you and someone else (a compiler vendor, say, or a developer of some other tool that processes source code) disagree on what is and isn't C++. The whole purpose of a standard is to provide definitive information that settles arguments like that. The standard's official title is a mouthful, but if you need to know it, you need to know it. Here it is: International Standard for Information SystemsProgramming Language C++. It's published by Working Group 21 of the International Organization for Standardization (ISO). (If you insist on being picky about it, it's really published by I am not making this up ISO/IEC JTC1/SC22/WG21.) You can order a copy of the official standard from your national standards body (in the United States, that's ANSI, the American National Standards Institute), but copies of late drafts of the standard which are quite similar (though not identical) to the final document are freely available on the World Wide Web. A good place to look for a copy is at the Cygnus Solutions Draft Standard C++ Page, but given the pace of change in cyberspace, don't be surprised if this link is broken by the time you try it. If it is, your favorite Web search engine will doubtless turn up a URL that works. As I said, The Design and Evolution of C++ is fine for insights into the language's design, and the standard is great for nailing down language details, but it would be nice if there were a comfortable middle ground between D&E's view from 10,000 meters and the standard's micron-level examination. Textbooks are supposed to fill this niche, but they generally drift toward the standard's perspective, whereby what the language is receives a lot more attention than why it's that way. Enter the ARM. The ARM is another book, The Annotated C++ Reference Manual, by Margaret Ellis and Bjarne Stroustrup (Addison-Wesley, 1990). Upon its publication, it became the authority on C++, and the international standard started with the ARM (along with the existing C standard) as its basis. In the intervening years, the language specified by the standard has in some ways parted company with that described by the ARM, so the ARM is no longer the authority it once was. It's still a useful reference, however, because most of what it says is still true, and it's not uncommon for vendors to adhere to the ARM specification in areas of C++ where the standard has only recently settled down. What makes the ARM really useful, however, isn't the RM part (the Reference Manual), it's the A part: the annotations. The ARM provides extensive commentary on why many features of C++ behave the way they do. Some of this information is in D&E, but much of it isn't, and you do want to know it. For instance, here's something that drives most people crazy when they first encounter it: class Base { public: virtual void f(int x); }; class Derived: public Base { public: virtual void f(double *pd); }; Derived *pd = new Derived; pd->f(10); // error! The problem is that Derived::f hides Base::f, even though they take different parameter types, so compilers demand that the call to f take a double*, which the literal 10 most certainly is not. This is inconvenient, but the ARM provides an explanation for this behavior. Suppose that when you called f, you really did want to call the version in Derived, but you accidentally used the wrong parameter type. Further suppose that Derived is way down in an inheritance hierarchy and that you were unaware that Derived indirectly inherits from some base class BaseClass, and that BaseClass declares a virtual function f that takes an int. In that case, you would have inadvertently called BaseClass::f, a function you didn't even know existed! This kind of error could occur frequently where large class hierarchies are used, so Stroustrup decided to nip it in the bud by having derived class members hide base class members on a per-name basis. Note, by the way, that if the writer of Derived wants to allow clients to access Base::f, this is easily accomplished via a simple using declaration: class Derived: public Base { public: using Base::f; // import Base::f into // Derived's scope virtual void f(double *pd); }; Derived *pd = new Derived; pd->f(10); // fine, calls Base::f For compilers not yet supporting using declarations, an alternative is to employ an inline function: class Derived: public Base { public: virtual void f(int x) { Base::f(x); } virtual void f(double *pd); }; Derived *pd = new Derived; pd->f(10); // fine, calls Derived::f(int), // which calls Base::f(int) Between D&E and the ARM, you'll gain insights into the design and implementation of C++ that make it possible to appreciate the sound, no-nonsense architecture behind a sometimes baroque-looking facade. Fortify those insights with the detailed information in the standard, and you've got a foundation for software development that leads to truly effective C++. Back to Item 49: Familiarize yourself with the standard library. Continue to Afterword