Index
[]Abstract data type. [See ]Accessor functions. [See ]Address arithmetic. [See ]]Array index calculation. [See ]Arraysas function arguments. [See ]]Audit trails, resource control. [See ] auto_ptr
Index
[]Base classes
Bibliography
Alexandrescu, Andrei. Modern C++ Design . Addison-Wesley, 2001.
Dewhurst, Stephen C. C++ Gotchas . Addison-Wesley, 2003.
Gamma, Erich, Richard Helm, Ralph Johnson, and John Vlissides. Design Patterns . Addison-Wesley, 1995.
Josuttis, Nicolai M. The C++ Standard Library . Addison-Wesley, 1999.
Meyers, Scott. Effective C++ , Third Edition. Addison-Wesley, 2005.
Meyers, Scott. Effective STL . Addison-Wesley, 2001.
Meyers, Scott. More Effective C++ . Addison-Wesley, 1996.
Sutter, Herb. Exceptional C++ . Addison-Wesley, 2000.
Sutter, Herb. More Exceptional C++ . Addison-Wesley, 2002.
Sutter, Herb. Exceptional C++Style . Addison-Wesley, 2005.
Sutter, Herb, and Andrei Alexandrescu. C++ Coding Standards . Addison-Wesley, 2005.
Vandevoorde, David, and Nicolai M. Josuttis. C++ Templates . Addison-Wesley, 2003.
Wilson, Matthew. Imperfect C++ . Addison-Wesley, 2005.
Index
[]]]Cast operatorsnew styleClass layoutClass objects. [See ]Classes]]Communication, with other programmersComplete specialization. [See ]Const member functionsConstructionConstructorsContravariance]]
Item 1. Data Abstraction
A "type" is a set of operations, and an "abstract data type" is a set of operations with an implementation. When we identify objects in a problem domain, the first question we should ask about them is, "What can I do with this object?" not "How is this object implemented?" Therefore, if a natural description of a problem involves employees, contracts, and payroll records, then the programming language used to solve the problem should contain Employee , Contract , and PayrollRecord types. This allows an efficient, two-way translation between the problem domain and the solution domain, and software written this way has less "translation noise" and is simpler and more correct.
In a general-purpose programming language like C++, we don't have application-specific types like Employee . Instead, we have something better: the language facilities to create sophisticated abstract data types. The purpose of an abstract data type is, essentially, to extend the programming language into a particular problem domain.
No universally accepted procedure exists for designing abstract data types in C++. This aspect of programming still has its share of inspiration and artistry, but most successful approaches follow a set of similar steps.
| Choose a descriptive name for the type. If you have trouble choosing a name for the type, you don't know enough about what you want to implement. Go think some more. An abstract data type should represent a single, well-defined concept, and the name for that concept should be obvious.
|
| List the operations that the type can perform. An abstract data type is defined by what you can do with it. Remember initialization (constructors), cleanup (destructor), copying (copy operations), and conversions (nonexplicit single-argument constructors and conversion operators). Never, ever, simply provide a bunch of get/set operations on the data members of the implementation. That's not data abstraction; that's laziness and lack of imagination.
|
| Design an interface for the type. The type should be, as Scott Meyers tells us, "easy to use correctly and hard to use incorrectly." An abstract data type extends the language; do proper language design. Put yourself in the place of the user of your type, and write some code with your interface. Proper interface design is as much a question of psychology and empathy as technical prowess.
|
| Implement the type. Don't let the implementation affect the interface of the type. Implement the contract promised by the type's interface. Remember that the implementations of most abstract data types will change much more frequently than their interfaces.
|
Item 2. Polymorphism
The topic of polymorphism is given mystical status in some programming texts and is ignored in others, but it's a simple, useful concept that the C++ language supports. According to the standard, a "polymorphic type" is a class type that has a virtual function. From the design perspective, a "polymorphic object" is an object with more than one type, and a "polymorphic base class" is a base class that is designed for use by polymorphic objects.
Consider a type of financial option, AmOption , as shown in .
Figure 1. Polymorphic leveraging in a financial option hierarchy. An American option has four types.
An AmOption object has four types: It is simultaneously an AmOption , an Option , a Deal , and a Priceable . Because a type is a set of operations (see , 93]).
Of course, there's a catch. For this leveraging to work, a properly designed polymorphic class must be substitutable for each of its base classes. In other words, if generic code written to the Option interface gets an AmOption object, that object had better behave like an Option!
This is not to say that an AmOption should behave identically to an Option . (For one thing, it may be the case that many of the Option base class's operations are pure virtual functions with no implementation.) Rather, it's profitable to think of a polymorphic base class like Option as a contract. The base class makes certain promises to users of its interface; these include firm syntactic promises that certain member functions can be called with certain types of arguments and less easily verifiable semantic promises concerning what will actually occur when a particular member function is called. Concrete derived classes like AmOption and EurOption are subcontractors that implement the contract Option has established with its clients, as shown in .
Figure 2. A polymorphic contractor and its subcontractors. The Option base class specifies a contract.
For example, if Option has a pure virtual price member function that gives the present value of the Option , both AmOption and EurOption must implement this function. It obviously won't implement identical behavior for these two types of Option , but it should calculate and return a price, not make a telephone call or print a file.
On the other hand, if I were to call the price function of two different interfaces to the same object, I'd better get the same result. Essentially, either call should bind to the same function:
AmOption *d = new AmOption;Option *b = d;d->price(); // if this calls AmOption::price...b->price(); // ...so should this!
This makes sense. (It's surprising how much of advanced object-oriented programming is basic common sense surrounded by impenetrable syntax.) If I were to ask you, "What's the present value of that American option?" I'd expect to receive the same answer if I'd phrased my question as, "What's the present value of that option?"
The same reasoning applies, of course, to an object's nonvirtual functions:
b->update(); // if this calls Option::update...d->update(); // ...so should this!
The contract provided by the base class is what allows the "polymorphic" code written to the base class interface to work with specific options while promoting healthful ignorance of their existence. In other words, the polymorphic code may be manipulating AmOption and EurOption objects, but as far as it's concerned they're all just Option s. Various concrete Option types can be added and removed without affecting the generic code that is aware only of the Option base class. If an AsianOption shows up at some point, the polymorphic code that knows only about Option s will be able to manipulate it in blissful ignorance of its specific type, and if it should later disappear, it won't be missed.