Professional C__ - Marc Gregoire [56]
Use templates when you want to provide identical functionality for different types. For example, if you want to write a generic sorting algorithm that works on any type, use templates. If you want to create a container that can store any type, use templates. The key concept is that the templatized structure or algorithm treats all types the same.
When you want to provide different behaviors for related types, use inheritance. For example, use inheritance if you want to provide two different, but similar, containers such as a queue and a priority queue.
Note that you can combine inheritance and templates. You could write a templatized queue that stores any type, with a subclass that is a templatized priority queue. Chapter 19 covers the details of the template syntax.
Provide Appropriate Checks and Safeguards
You should always design your programs to be as safe as possible for use by other programmers. The most important aspect of this guideline is to perform error checking in your code. For example, if your random number generator requires a non-negative integer for a seed, don’t just trust the user to correctly pass a non-negative integer. Check the value that is passed in, and reject the call if it is invalid. A call can be rejected by returning an error code or by throwing an exception. Your library should never output error messages to a console or show an error in a message box or terminate the calling application.
As an analogy, consider an accountant who prepares income tax returns. When you hire an accountant, you provide him or her with all your financial information for the year. The accountant uses this information to fill out forms from the IRS. However, the accountant does not blindly fill out your information on the form, but instead makes sure the information makes sense. For example, if you own a house, but forget to specify the property tax you paid, the accountant will remind you to supply that information. Similarly, if you say that you paid $12,000 in mortgage interest, but made only $15,000 gross income, the accountant might gently ask you if you provided the correct numbers (or at least recommend more affordable housing).
You can think of the accountant as a “program” where the input is your financial information and the output is an income tax return. However, the value added by an accountant is not just that he or she fills out the forms. You choose to employ an accountant also because of the checks and safeguards that he or she provides. Similarly in programming, you should provide as many checks and safeguards as possible in your implementations.
Under no circumstances should a library decide to terminate the calling program. For the purpose of writing a library, you may safely forget that an exit() function or anything resembling its functionality, exist.
There are several techniques and language features that help you incorporate checks and safeguards in your programs. First, you can return an error code or a distinct value like false or nullptr or throw an exception to notify the client code of an error. Chapter 10 covers exceptions in detail. Second, use smart pointers that help you manage your dynamically allocated memory. Conceptually, a smart pointer is a pointer to dynamically allocated memory that remembers to free the memory when it goes out of scope. Smart pointers are discussed in Chapter 21. Third, use safe memory techniques as discussed in Chapter 21.
Design Usable Interfaces
In addition to abstracting and structuring your code appropriately, designing for reuse requires you to focus on the interface with which programmers interact. If you hide an ugly implementation behind a pretty interface, no one needs to know. However, if you provide a beautiful implementation behind a wretched interface, your library won’t be much good.
Note that every subsystem and class in your program should have good interfaces, even if you don’t intend them to be used in multiple programs. First of all,