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
It's not that hard once you understand the design goals that forged C++ into what it is. Foremost amongst those goals are the
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 struct
s, 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
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 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
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
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
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
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
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
Enter the ARM. The ARM is another book,
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
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
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
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
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
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