Important Announcement
PubHTML5 Scheduled Server Maintenance on (GMT) Sunday, June 26th, 2:00 am - 8:00 am.
PubHTML5 site will be inoperative during the times indicated!

Home Explore All of Programming [ PART I ]

All of Programming [ PART I ]

Published by Willington Island, 2021-09-02 03:28:09

Description: All of Programming provides a platform for instructors to design courses which properly place their focus on the core fundamentals of programming, or to let a motivated student learn these skills independently. A student who masters the material in this book will not just be a competent C programmer, but also a competent programmer. We teach students how to solve programming problems with a 7-step approach centered on thinking about how to develop an algorithm. We also teach students to deeply understand how the code works by teaching students how to execute the code by hand.

Search

Read the Text Version

17.3.5 Dependent Type Names Require Keyword typename In C and C++, the compiler must figured out whether a name refers to a type or not to determine how to parse the code. Consider examples such as x & y, x * y, and x(y). Each of these fragments of code is legal whether x names a type or is a variable but has a completely different meaning. In the case of x & y, this code fragment either declares an x reference called y (if x names a type) or is an expression using the binary & (bit- wise and) operator on the variables x and y. Likewise, x * y either declares a pointer to an x or multiplies x by y. The expression x(y) either creates an unnamed temporary of type x (passing in y to its constructor) or calls a function named x with argument y. While this necessity may be a bit of a pain in the normal course of C and C++ compilation, the compiler can typically simply check to see if x names a type or not. However, if T is a template parameter that names a type (e.g., we are inside the scope of template<typename T>), then the compiler has a difficult time determining if T::x names a type or a variable. Likewise, Something<T>::x is difficult to figure out. In these two situations, we call x a dependent name, as its interpretation depends on what T is. Whenever you use a dependent name for a type, you need to explicitly tell the compiler that it names a type by placing the typename keyword before the name of the type. For example: 1 template<typename T> 2 class Something { 3 private: 4 typename T::t oneField 5 typename anotherTemplate<T>::x twoField; public:

6 public: T::t someFunction(typename T::t x) { 7 typename 8 typename T::t someVar; 9 //some code 10 } 11 }; This example (which is purely intended to demonstrate the syntax not any useful template or class), shows five uses of dependent type names: declaring the two fields, the return type of the function, the parameter type of the function, and the local variable in the function. Each of these requires us to explicitly add the typename keyword, as shown in the example. We will note that there are a variety of complexities in the rules related to dependent names, which we are not going to delve into here. There are also circumstances where a dependent name has a templated class or function inside of it, and the compiler must explicitly be told that name refers to a template (via the template keyword) to disambiguate the < that encloses the template’s arguments from the less-than operator. These complexities are not really required for a basic grasp of C++ programming and template use—the above description of the rules are sufficient for what we will do in this book. However, you should know that this description is not the entire “can of worms” if you plan to proclaim yourself as a “C++ expert.” 17.3.6 You Can Provide an Explicit Specialization You can explicitly write a specialization for a template— providing specific behavior for particular values of the template arguments. Often explicit specialization is performed to provide a more efficient implementation of the exact same behavior as the “primary template” (the

original template, which you are explicitly specializing). However, there is no rule to enforce this behavior. You could explicitly specialize a template to behave completely differently from the primary template. Doing so would generally be a bad idea, as it would confused anyone (including yourself) using the code. Explicit specializations may either be partial (you only specialize with respect to some of the parameters, leaving the resulting specialization parameterized over the others) or complete (you specialize all of the parameters). If you use partial specialization, you should be aware of the rules that C++ uses to match template instantiations to the partial specializations you have written, which we will not go into here. In fact, we will not say much about this topic other than that it exists. We will mention it briefly again Section E.6.11 when we discuss C++11’s variadic templates, but otherwise we will not have much need for it in this book. 17.3.7 Template Parameters for Functions (But Not Classes) May Be Inferred. When you use a templated function, you can omit the angle brackets and template arguments in cases where the compiler can infer them—that is, if the compiler can guess what to use based on other information, such as the types of the parameters to the function in question. However, we recommend against this practice. It is much better to be explicit and make sure you and the compiler both agree on what types are being used. Being explicit also helps with readability—someone else (or yourself later) examining your code knows exactly what is happening. Furthermore, the compiler may infer a perfectly legal type that you did not intend. We mostly

mention this rule in case you see code with it, so that you will not be surprised but instead will understand what is going on. For templated classes, the arguments must always be explicitly specified. The compiler will never try to infer them. 17.4 The Standard Template Library C++ has a Standard Template Library (STL), which provides a variety of useful templated classes and functions. We will obviously not describe everything in the STL here; however, we will mention a few important and useful things from it. We will see some other parts of the STL as we progress through Part III and learn about data structures and algorithms that have STL implementations. 17.4.1 The std::vector Templated Class One useful class in the STL is the std::vector<typename T> templated class.2 A vector is similar to an array in that it stores elements of some other type—namely, the type that is its template parameter T. Like an array, the vector’s elements may be accessed with the [] operator. You should #include <vector> if you want to use this class. Unlike an array, the size of a vector can be changed via methods such as push_back (which adds an element to the end of the vector) and insert (which inserts an element at an arbitrary3 position). The size of the vector can also be reduced by removing elements from it with methods such as pop_back (which removes the last

element of the vector) and erase (which removes an arbitrary element). vectors also provide a variety of features that C’s arrays (which are just pointers) do not. C++’s vectors provide a copy constructor and assignment operator, so that a vector can be copied, making copies of all of its elements (of course, a destructor is also provided to free up the memory internally allocated by the vector). Additionally, a vector keeps track of its size, which can be queried with the size method. The vector template also provides for overloaded operators to compare two vectors for equality or ordering. The == operator checks if the two vectors are the same size and, if so, compares each element in its left-hand operand to the corresponding element in the right-hand operand for equality. Of course, this comparison requires the == operator to be defined on Ts, which it uses to compare the individual elements to each other. The == operator for vectors is a great example of the template rule that we discussed in Section 17.3.3—that type checking for templates only happens on the specializations that are actually used. The STL can define the vector template despite the fact that not all types have == defined on them. We can then instantiate the vectors with any type. For any of these types (T) that do define ==, we can compare vector<T>s for equality. We cannot compare vectors for equality if the type of objects they hold does not have == defined on it. vectors also have a < operator, which orders two vectors (holding the same type) lexicographically. Recall that in Section 10.1.3, we described lexicographic ordering as being “what you would think of as ’alphabetical ordering’ but extended to encompass the

fact that strings may have non-letters.” Lexicographic ordering is actually a bit more general than that. It applies to any sequence and is determined by comparing the each element of the sequence in order from the first towards the last. If two elements are equal, then the comparison continues to the next pair of elements. When two differing elements are encountered, their order determines the lexicographic ordering of the entire sequence. Basically, it is “how alphabetical ordering works” but generalized to any sequence of any type of thing, where that type of thing can be compared for ordering. vectors are incredibly useful and typically preferred over “bare” arrays in C++. You can read more about the particulars of them in their online reference: http://www.cplusplus.com/reference/vector/vector/. 17.4.2 The std::pair Templated Class Another useful templated class is the std::pair class, which is templated over two types, T1 and T2. All the pair class does is provide a way to pair two objects—one of type T1 and one of type T2—together into a single object. The elements of the pair can then be accessed via the public fields first and second. Grouping two items together into one is a simple concept but quite useful. There are many cases in programming languages in which you are restricted to “one thing” but really want to have two. For example, we can only return one value from a function. Sometimes, we find ourselves wanting to return two values, but we cannot do that in C or C++. However, we can make the return type of the function a pair, combine the two values into a pair, and return that pair. Another example appears in a variety of data structures, such as the

std::vector we just discussed. Each index of a vector (or array) can only hold one value. However, we might want to have a vector (or array) of pairs of data. 17.4.3 Iterators C++’s STL has a variety of “container” classes (classes whose job is to hold other data). Many of these container classes are designed to provide similar interfaces to each other. Among other reasons, providing a similar interface between these classes allows code that uses them to be templated over the container class, allowing it to work flexibly with any of them. In fact, the STL provides a variety of algorithms (which we will discuss shortly) designed to work with most of its containers. Many algorithms need to iterate over the data structure they work on (as you have likely experienced extensively by now). For vectors and arrays, accessing a particular element by numerical index (i.e., myVector[i]) is quite efficient. However, as we shall see in Part III, there are other data structures where this style of access is quite inefficient (we will also formalize what we mean by “efficient” in Chapter 20)—the data structure has to internally iterate through its elements to find the element in question. That is, if we wrote: 1 for (int i = 0; i < someThing.size(); i++) { 2 x = someThing[i]; 3} Then the someThing[i] operation (which would be an overloaded operator in whatever type someThing is) may take significant computation to get the proper element. Often in these situations, going from one element to the “next” one is still efficient—the inefficiencies arise from seeking an arbitrary element where the “go to next”

operation must be done several times internally. If we were willing to make the data structure expose its internal representations to the external code that needs to iterate over it, we could solve this inefficiency—our code could keep track of the internal state of the structure and perform efficient operations to go from one element to the next. Of course, exposing the internal representations of a class to the rest of the code goes against the principles of abstraction and object-oriented programming. The approach C++ takes to resolve this tension between good abstraction and high performance is to provide an abstraction called an iterator. An iterator is a class (typically internal to a data structure) that encapsulates the state of the internal traversal while providing an implementation-independent interface to external code. Put a different way, the iterator (which is a class inside of the data structure—thus it is fine for it to know the internal representation details) tracks where the traversal is in whatever fashion is efficient for the data structure it belongs to but gives a simple interface to other code. It is easiest to see how an iterator works by an example. We will start with a std::vector, since that is the data structure we are familiar with: 1 std::vector<int>::iterator it = myVector.begin() 2 while (it != myVector.end()) { 3 std::cout << *it << \"\\n\"; 4 ++it; 5} The type std::vector<int>::iterator is the iterator class defined inside of vector. We initialize it with myVector.begin() (myVector is a std::vector<int> declared and filled in somewhere before the example begins). The begin function in vector (and other classes

that support iterators) returns an iterator positioned at the start of the data structure. In the particular case of a vector, the iterator might be implemented as simply holding the current index (as indexing is efficient), in which case the iterator returned by begin would hold 0. We then have a loop in which we compare our iterator (it) against the return result of myVector.end(), which returns an iterator that is past the last element of the data structure (if we think of our vector’s iterator as holding an index, then end would return the size of the vector, as size - 1 is the last valid index). If it is equal to myVector.end(), then we are done with the traversal. If it is not equal to myVector.end(), then we enter the body of the while loop and print out an element of the vector (and a newline). We access the particular element of the vector the iterator refers to by dereferencing the iterator with its overloaded * operator, which returns a reference to that element. At the end of the loop, we increment the iterator with ++it, which moves it to the next element. If we think of the iterator for a vector as internally holding an index into the vector, this operation would increment that integer. You may wonder why we have written ++it rather than more familiar it++. They are functionally equivalent, but ++it is more efficient due to the difference in the semantics of the operators. The postfix increment operator (it++) evaluates to the value before the increment was performed, while the prefix increment operator (++it) evaluates to the value after the increment is performed. Although this distinction seems minor, the postfix increment requires the operator to make a copy of the original object, perform the increment, then return the copy of the object (which would then be destroyed as we are not using it). In general, you should use the prefix

increment operator whenever the increment is on an object type to avoid these unnecessary copies and destructions. This loop with a vector may seem silly—after all, we can just write a for loop from 0 to the size of the vector and make use of the [] operator to get a reference to a particular element. However, if we were to use some other data structure (such as a std::set, which we will talk about in Section 20.5, we could not index it with an integer, but we could use almost identical code with iterators: 1 std::set<int>::iterator it = mySet.begin(); 2 while (it != mySet.end()) { 3 std::cout << *it << \"\\n\"; 4 ++it; 5} In fact, if you look carefully, you will notice that the only substantiative change (as opposed to just changing the variable’s name) between the std::set example and the std::vector example is the type of it. We could even write a templated function: 1 template<typename T> 2 void printElements(T & container) { 3 typename T::iterator it = container.begin(); 4 while (it != container.end()) { 5 std::cout << *it << \"\\n\"; 6 ++it; 7} 8} We could then use this templated function to print elements from any container that supports iterators. Observe how we have put abstraction to good use: separating the interface for iterating (efficiently) across a collection of elements from the implementation of doing so.

Of course, the previous example is a bit unappealing to the meticulous programmer, as it is not const-correct (that is, container should be a const T &, as we do not modify its elements). However, begin returns an iterator that allows the elements it references to be modified (as it returns a reference, not a const reference), so we cannot call it on a const object. We can make this code more correct by using a const_iterator, which is just like an iterator except that dereferencing it provides a const reference to the underlying element: 1 template<typename T> 2 void printElements(const T & container) { 3 typename T::const_iterator it = container.begin 4 while (it != container.end()) { 5 std::cout << *it << \"\\n\"; 6 ++it; 7} 8} Note that there is no mystery or magic in begin returning an iterator in the first examples and const_iterator in this most recent example. This difference is just a result of the fact that begin is overloaded with the following two signatures (in the STL classes and in other well designed classes): 1 iterator begin(); 2 const_iterator begin() const; As we discussed previously, these differ in the type of their this argument and thus are a legal overloading. The second has this as a pointer to a const object. Therefore, when we invoke begin on a const object, overloading resolution selects the second, and the return type is a const_iterator.

C++ has a variety of kinds of iterators, which vary in how they can traverse their data structure. Some iterators are restricted in what they can do—forward iterators can only move forward (via ++), and not backwards (via --), while bidirectional and random access iterators can go both forwards and backwards. However, random access iterators can move forwards or backwards by more than one element at a time (via += and -=), which the other types cannot. We are typically going to work only with basic iterator functionality (comparison for equality, dereference, increment), so we are not going to be greatly concerned with the fancier features of the various types. Of course, if you want to know more about them, you can read plenty online: http://www.cplusplus.com/reference/iterator/. One other important consideration when using an iterator is what happens when the underlying data structure changes by having elements inserted or removed. The simplest way to see why such a modification might pose a problem is to consider the case where the element an iterator refers to is removed while the iterator still refers to it. Now, the iterator references something no longer in the data structure—does it even make sense to dereference this iterator? Can we move from the position of a non existent element in a meaningful way? Whenever a modification occurs, an iterator may be invalidated by that modification—use of an invalidated iterator is incorrect (that is, it is an error in your program —which will likely crash, but even if it does not, your program is broken). The specific circumstances under which an iterator is invalidated varies from data structure to data structure, thus you should consult the documentation for the class and operation you are using to determine its impact on the validity of your iterators.

17.4.4 Algorithms from the STL The STL also provides a variety of algorithms (for most of these, you should #include <algorithm>). The simplest of these are the min and max algorithms, which choose the smallest/largest of their two parameters. There are two versions of these templates. The first simply compares the two parameters with the < operator. The second allows for a custom comparison function to determine the ordering. The second version (which allows a custom ordering) is implemented by having a second template parameter that specifies a class with an overloaded () operator (the “function call” operator). Overloading the function call operator on a class allows the programmer to place parentheses with arguments after an instance of that class in a way that looks like a function call— resulting in a call to the overloaded operator. The function call operator can be overloaded with any number of parameters. An object with “function call” behavior (i.e., an overloaded function call operator) is referred to as a function object.4 To see an example of the min algorithm with a custom comparison function object, we can start with a class that compares two std::strings in a case- insensitive fashion: 1 class OrderIgnoreCase { 2 public: 3 bool operator()(const std::string & s1, 4 for (size_t i = 0; i < s1.size() && i 5 char c1 = tolower(s1[i]); 6 char c2 = tolower(s2[i]); 7 if (c1 != c2) { 8 return c1 < c2; 9} 10 }

11 return s1.size() < s2.size(); 12 } 13 }; In this example, we overload the function call operator to take two const std::string &s, which are the strings to compare for ordering. The syntax may seem a bit odd; however, nothing is actually unusual—the name of the operator is just operator(), which is then followed by the parameter list in parentheses. The function then compares the strings ignoring the case of each letter (by converting the letters to lower case before comparing them to each other). We could then use this class to with the min algorithm to find the minimum of two strings and ignore case: 1 std::min<std::string, OrderIgnoreCase>(str1, str2 Of course, the STL has a variety of other algorithms beyond just min and max. Many of these make use of iterators (allowing them to work on a variety of containers) and function objects (allowing them to abstract out how various operations are performed, giving them more flexibility). We recommend consulting their documentation to find out details about any of them: http://www.cplusplus.com/reference/algorithm/. 17.5 Practice Exercises Selected questions have links to answers in the back of the book. • Question 17.1 : Why are templates useful?

• Question 17.2 : What is parametric polymorphism? • Question 17.3 : When is a template type checked? What are the implications of this for what is and is not legal in templated code? • Question 17.4 : What is wrong with writing std::vector<std::pair<int,int>> ? • Question 17.5 : Write a templated function that takes in an array of Ts and an int for the number of items in the array and returns a count of how many of the items are “even”. For this function, “even” means that an item mod 2 is equal to 0. You can assume that this template will only be applied to types where % is overloaded on Ts and ints. • Question 17.6 : What does it mean for a template parameter to be “inferred”? Under what circumstances will this happen? • Question 17.7 : Rewrite your countEven function to operate on a std::vector instead of an array. Your function should take its parameter as a const reference to a vector (i.e., a conststd::vector<T> &). • Question 17.8 : Rewrite your countEven so that it can operate on the iterators within any type (i.e., its two parameters are T::iterators). • Question 17.9 : In the previous question, why did you have to use typename in the parameter declarations? • Question 17.10 : Write a function vector<pair<T2, T1> > flipPairs(const vector<p air<T1, T2> > & v), which takes a vector of pairs, and returns a vector of pairs where the first and second element have been switched. 16 Strings and IO Revisited18 Inheritance Generated on Thu Jun 27 15:08:37 2019 by L T XML



II C++17 Templates19 Error Handling and Exceptions

Chapter 18 Inheritance C++, like many object-oriented languages, supports inheritance. Inheritance is the ability to declare a class (called the child class or subclass) in such a way that it obtains all of the fields and methods of another class (called the parent class or superclass). The child class can have more fields and methods of its own added to it and can override the behavior of the methods it inherited. Inheritance is best used when two classes exhibit an is-a relationship. For example, if we were designing a set of components for a graphical interface, we might have a class for a Button. If we were to want to create a class for a button with an image on it, (e.g., an ImageButton class), we would want to use inheritance. An ImageButton is a Button; it just adds more features (having an image instead of just text) and overrides some behaviors of the parent class (how it is drawn). In this use of inheritance, Button is called the parent class, super class or base class, and ImageButton is called the child class, subclass or derived class. We would say that ImageButton inherits from or extends Button. Although the ImageButton in our example will change some behaviors, much of the code for the original Button class (e.g., the code to process the button being clicked and execute some other function in response to it) remains the same. Using inheritance allows the ImageButton to inherit this functionality from the Button class, rather than requiring the programmer to rewrite (or copy and paste) the code in multiple places. Avoiding this

duplication of code makes our program easier to write correctly and easier to maintain. Contrast the is-a relationship between an ImageButton and a Button with a has-a relationship. A Button has a string for the text that appears on it, but we would not say that “A Button is a string.” Whenever two types exhibit as has-a relationship, inheritance is inappropriate. Instead, one should have a field for the other—the Button class should declare a field of type string for its text. 18.1 Another Conceptual Example As another example of inheritance (which we will use in many of the examples throughout this chapter), consider the BankAccount class we have used in prior examples. If we were actually making a program to manage bank accounts, we might have specific subtypes of bank account, which have additional properties and slightly different behaviors. For example, we might have an InvestmentAccount, which has a list of stocks that are in the account in addition to the cash balance. In this case, inheritance is again appropriate—an InvestmentAccount is a BankAccount. We are not limited to one level of inheritance. When appropriate, we can inherit again from a class that is itself a child class, using it as the parent class of a third class. In our BankAccount/InvestmentAccount class, we might have a special type of InvestmentAccount that allows people to “trade on margin” (i.e., buying stocks with borrowed money), called a MarginAccount. In such a case, MarginAccount could extend InvestmentAccount.

Figure 18.1: A diagram of the example BankAccount inheritance hierarchy. We can also make many different child classes with the same parent class. For example, in addition to having MarginAccount inherit from InvestmentAccount, we could also have RetirementAccount inherit from InvestmentAccount. All of these classes form an inheritance hierarchy—the collection of all classes that share a common “ancestor” class (parent class, “grandparent” class, etc.). Figure 18.1 shows a diagram of the classes in our example inheritance hierarchy. In this diagram, each class is represented by three boxes, one with the class’s name, a second with the class’s fields, and a third with the class’s methods. The arrow indicates the “inherits from” (“is-a”) relationship between the classes. For example, the BankAccount class has two fields: a double for the balance, and an unsigned long for the account number. It also has three methods, one to deposit money, one to withdraw money, and one to get the account balance.

The InvestmentAccount class inherits the fields and methods BankAccount has, so we do not show them again. However, we add some new fields (a vector of pair<Stock *, double>s, which would contain each Stock and how many shares the account has) and how many trades were executed this month (so we can charge different commission rates based on how frequently the owner trades). We then add some methods to buy and sell stock as well as compute the market value (the sum of the values of the stocks and the cash balance). The MarginAccount inherits from InvestmentAccount. This class inherits the balance, acctNumber, stocks, and tradesThisMonth fields from InvestmentAccount. It also inherits the deposit, withdraw, sellStock, getBalance, and getMarketValue methods. However, this class needs to specify different behavior for the buyStock method rather than using the behavior it would inherit from its parent class (i.e., rather than indicating an error if there is not enough cash in the account, it could borrow against the margin). Accordingly, MarginAccount would override the buyStock method—providing its own definition in place of the one it would normally inherit from its parent. Similarly, RetirementAccount inherits from InvestmentAccount but overrides deposit (to check the contribution against the annual contribution limit) and withdraw (as withdrawing from a retirement account has different tax consequences than withdrawing from a regular account). 18.2 Writing Classes with Inheritance To use inheritance in C++, the class declaration for the child class has a colon, followed by an access specifier, and then the parent class name between the class’s

name and the open curly brace of the class declaration. In our BankAccount example, we would write: 1 class BankAccount { 2 double balance; 3 unsigned long acctNumber; 4 public: 5 void deposit(double amount); 6 double withdraw(double amount); 7 double getBalance() const; 8 }; 9 class InvestmentAccount : public BankAccount { 10 vector<pair<Stock *, double> > stocks; 11 unsigned tradesThisMonth; 12 public: 13 void buyStock(Stock whichStock, double numShare 14 void sellStock(Stock whichStock, double numShar 15 double getMarketValue() const; 16 }; We would also add constructors and destructors to each class as needed and write the method bodies (either inline or separately, as we have seen previously). Observe that we do not re-declare the fields or methods that the child class is inheriting from its parent. In fact, if we do declare fields of the same name as those in the parent class, we end up with objects that have two different fields of the same name. They are distinct in that they have different fully qualified names. In this example, if we added a duplicated field called balance, code in InvestmentAccount would reference it by default, and would refer to the inherited field by BankAccount::balance. However, creating multiple fields with the same name in this fashion is generally an indication of poor design or a lack of understanding of how inheritance works. The public access specifier in the inheritance declaration (: public BankAccount) specifies how the

access of inherited members should be changed. Using public inheritance, as we have done in this example, specifies that the access should be unchanged from that declared in the parent class: public members remain public, and private members remain private. Alternatively, one may use private inheritance (by writing : private BankAccount), in which all inherited members become private in the child class. A third option is available, using an access specifier we have not yet seen: protected. Using protected inheritance makes public members of the parent class into protected members of the child class. Members of a class may be declared protected, which means that they may be accessed by members of the class or by members of any of its child classes. That is, if we write: 1 class A { 2 protected: 3 int x; 4 }; 5 class B : public A { 6 void someFunction() { 7 x++; 8} 9 }; The reference to x inside of class B (which is a child class of A) is legal. The field x is protected, so the child classes can access it. However, code outside of classes A and B (and any other child classes of B) would not be able to access x directly. Of course, classes can also declare other classes as friends to grant them access to private/protected members. C++ has one slightly subtle restriction on the use of protected members in a child class: they may only be accessed through a pointer/reference/variable of the child class’s own type. In the example above, the access to x is performed through the implicit this pointer, which has

type B const *, so it is legal according to this rule. However, if we wrote: 1 class A { 2 protected: 3 int x; 4 }; 5 class B : public A { 6 void someFunction(A * anA) { 7 anA->x++; 8} 9 }; We would instead receive the following compiler error: In member function ’void B::someFunction(A*)’: error: ’int A::x’ is protected error: within this context Here, the problem is that we are accessing the field x in class B through a pointer that is of type A *. 18.3 Construction and Destruction When objects that use inheritance are constructed or destroyed, the constructors and destructors for their parent classes are run to initialize/cleanup the parent portion of the objects. The first thing that happens when creating a new object is that the constructor for its parent- most ancestor is run to initialize that portion of the object. Then the constructor for the next closest ancestor is run, and so on. Finally, the constructor for the class itself is run to initialize that class. To be more concrete, consider an inheritance hierarchy in which we have class A (which inherits from nothing), class B, which inherits from A, and class C, which inherits from B. Whenever we create an object of type C, then first thing that happens is that we run the constructor for A, followed by the constructor for B, and finally the constructor for C. In destroying an object,

the destructors are run in the reverse order: from the class itself, up the chain of parent classes (in this example: C, B, then A). In C++, the type of an object actually changes during this construction process. In the example above, the new operator allocates space for an entire C object, but the type of the object is initially set to A. Only after the constructor for A finishes, does the type of the object change to B. The type of the object remains as B while the constructor for B executes. Finally, the type of the object becomes C after B’s constructor completes, right before C’s constructor begins. At this point, the actual type of the object remains the same until it is destroyed. We will show the type of an object in our diagrams at the top of the object’s box with blue text on a gray background. During destruction, the process happens in reverse; however, it stops if any parent class’s destructor is trivial. When destroying a C, C’s destructor executes first (assuming it is nontrivial). The code in C’s destructor executes, followed by the destructors for any fields in C (in reverse order of construction as usual). If the parent class’s destructor (in this example, B) is nontrivial, then it is called. The type of the object changes from C to B once control enters B’s destructor, right before any of the code in B’s destructor begins. After B’s destructor completes and its fields are destructed, A’s destructor must be run (unless it is trivial). Once control enters A’s destructor, the type of the object changes to A. After all nontrivial destructors complete, the memory allocated to the object can be released. While the actual type of the object during this process may seem a subtle pedantic distinction, we will see that it can actually make a difference in how code executes when we learn a bit more about inheritance.

We will briefly note that C++’s design choice in this regard differs drastically from Java’s (Java is another object-oriented language). If we were to examine our example in Java, we would still run the constructors in the same order (A, then B, then C); however, the object would have type C as soon as it is created, before entering A’s constructor. Java does not have destructors in the same fashion as C++. All of the above discussion has referenced calling the constructor of a particular class. However, in C++, it is possible to overload the constructor, meaning there may be multiple ones available. This possibility raises the questions of how the programmer can select a particular constructor for the parent class and how the programmer can pass in the desired arguments to the parent class constructors that take them. If the programmer does not explicitly specify a call to the parent class’s constructor, then the default constructor is implicitly used. If there is not default constructor (or if the default constructor is private), then the compiler will produce an error. If the programmer wishes to call some other constructor explicitly, she writes the call to the parent class’s constructor as the first element of the initializer list, by writing the parent class’s name, then parentheses with the argument list. For example, we might write: 1 class BankAccount { 2 double balance; 3 unsigned long acctNumber; 4 static unsigned long nextAccountNumber; 5 public: 6 BankAccount() : balance(0), acctNumber(nextAcco 7 nextAccountNumber++; 8} 9 BankAccount(double b) : balance(b), acctNumber 10 nextAccountNumber++; 11 }

12 }; 13 class InvestmentAccount : public BankAccount { 14 vector<pair<Stock *, double> > stocks; 15 unsigned tradesThisMonth; 16 public: 17 InvestmentAccount() : tradesThisMonth(0) { } 18 InvestmentAccount(double balance) : BankAccount 19 tradesThisM 20 }; Here, the BankAccount class has two constructors: a default constructor and one that takes the initial balance as a double. The InvestmentAccount class (which inherits from BankAccount) also has two constructors. In InvestmentAccount’s default constructor, the programmer has not written any explicit call to any of BankAccount’s constructors, so an implicit call to the default constructor is used (as if it were the first element of the initializer list). The second (which takes a double for the initial balance as its own arguments) passes that argument to the BankAccount constructor that takes a double for the initial balance. Note that destructors do not take arguments, so there is no issue of specifying which one to call or what arguments to pass it. Video 18.1: Constructing and destroying objects that use inheritance. Video 18.1 shows what happens during object construction and destruction when the classes involved make use of inheritance. 18.4 Subtype Polymorphism In Chapter 17, we learned that one way programmers can increase code reuse (and thus reduce code duplication) is through polymorphism—which allows the same code to operate on multiple types. The form of

polymorphism we saw in Chapter 17 is parametric polymorphism because the type is a parameter (to the templated class/function). Another form of polymorphism, which is related to inheritance, is subtype polymorphism. Subtype polymorphism arises when one type (InvestmentAccount) is a subtype of another type (BankAccount), meaning that an instance of InvestmentAccount is substitutable for an instance of BankAccount. In OO languages such as C++ or Java, a child class is a subtype of its parent class. By the nature of inheritance we are guaranteed that anything that is legal to do to the parent class (access a particular field, call a particular method, etc.) is also legal to do to the child class. If the parent class has a field called x, we know that the child class does as well because it inherited that field. Likewise, if the parent class has a method f(int), we know the child class has such a method as well because it inherited that method— although it may override it providing a different behavior. Note that in C++, polymorphism is restricted by the access modifier used in inheriting the parent class. If public inheritance is used (which is the most common way), then polymorphism may be freely used anywhere. If private or protected inheritance is used, then polymorphism is only permissible where a field with that access restriction could be used (in the class or its friends for private inheritance, or in the class, its subclasses, or any of their friends for protected inheritance). If the preceding portions of this paragraph seem a bit complex, do not worry too much, as they are only really relevant if you have a compelling need for non-public inheritance. In C++, subtype polymorphism allows the programmer to treat an instance of a child class as if it

were an instance of one of its parent classes. However, polymorphism is only applicable when used with pointers or references. Concretely, if class A is the (public) parent class of class B, then the following code is legal: 1 void f(A * a) { 2 ... 3} 4 void g(B * b) { 5 f(b); //uses polymorphism 6} Here, the function f is declared to take a pointer to an A. In g, b is a pointer to a B. However, we can pass b as the argument to f. Likewise, we can place pointers to Bs into an array of pointers to As or assign a pointer to a B to a variable whose type is a pointer to an A. Remember that inheritance reflects an “is-a” relationship. The fact that a B “is-an” A means that the fact that we can use a B as an A is quite natural. Returning to our earlier example of an InvestmentAccount and a BankAccount, it makes sense that we can use an InvestmentAccount in any context where we need a BankAccount. If we have an array (or vector) of BankAccount *s, then placing an InvestmentAccount * into that array makes perfect sense—it is a BankAccount. To further see the benefits of subtype polymorphism, consider if our BankAccount class had a method to accrue interest: 1 class BankAccount { 2 double interestRate; 3 //other fields elided 4 public: 5 //other methods and constructors elided 6 void accrueInterest(double fractionOfYear) { 7 balance += balance * interestRate * fractionO 8} 9 };

Now, suppose that we have a vector<BankAccount *> allAccounts, which contains all of the BankAccounts that our bank software tracks. Polymorphism allows us to place any subclass of BankAccount into allAccounts. For example, we might have the following code: 1 class Bank { 2 vector<BankAccount *> allAccounts; 3 public: 4 InvestmentAccount * createInvestmentAccount(dou 5 InvestmentAccount * newAccount = new Investme 6 allAccounts.push_back(newAccount); //use poly 7 return newAccount; 8} 9 void accrueInterestOnAllAccounts(double fractio 10 vector<BankAccount *>::iterator it = allAccou 11 while (it != allAccounts.end()) { 12 BankAccount * currentAccount = *it; 13 currentAccount->accrueInterest(fractionOfYe 14 ++it; 15 } 16 } 17 }; In this example, we have a Bank class (which tracks all of the accounts at one bank). This class has the allAccounts vector, which contains a pointer to every account at the bank. We have a method that creates a new investment account and adds the pointer to that account to the allAccounts vector before returning it to the caller. Adding this pointer (which is an InvestmentAccount * to the vector (which holds BankAccount *s) is a use of polymorphism. The compiler allows the implicit conversion from an InvestmentAccount * to a BankAccount * because it knows that an InvestmentAccount is a BankAccount.

The second method shows how polymorphism helps us avoid duplicating code. In this method (which we would call at the end of each month), we accrue interest for every account at the bank. Even though the pointers in the vector may actually point at InvestmentAccounts, RetirementAccounts, or MarginAccounts (in addition to plain BankAccounts), we can iterate over them all with one iterator and call accrueInterest on each of them. This use of polymorphism also provides benefits if we need to expand our code later. If our bank adds a new type of account (which also extends BankAccount), we do not need to change the accrueInterestOnAllAccounts method at all—it just handles that new account type naturally. The reason we can only apply polymorphism to pointers and references is a matter of implementation. An InvestmentAccount takes more space (in memory) than a BankAccount. As such, if we have an InvestmentAccount directly (i.e., not via a pointer or reference), it needs a “bigger box” than a BankAccount. If a frame or object is laid out for a BankAccount, it will only have a box of a size appropriate for a BankAccount, not an InvestmentAccount. However, pointers are the same size regardless of what they point to. We will learn more about the implementation details (what happens “under the hood”) to make inheritance and polymorphism work in Chapter 29. When we deal with pointers (or references) to objects in the presence of polymorphism, it is important to understand the difference between the static type and the dynamic type of the object it points at. The static type is the type obtained by the type checking rules of the compiler, which only uses the declared types of variables. The dynamic type of the object is the type of object that is actually pointed at.

For example, consider if we wrote BankAccount * b = new InvestmentAccount();. Here, the static type of *b is BankAccount—b is declared as a pointer to a BankAccount, so no matter what type it actually points at, the static type of *b is always a BankAccount. However, the dynamic type of *b is InvestmentAccount. If we drew the execution of this code by hand, we would see that the arrow in b’s box points at an InvestmentAccount object. This distinction is important because the compiler only works with the static types. When the compiler type checks the program, it must ensure that method calls (and field accesses) are legal based only on the static types. If we tried to call b->buyStock(someStock,amount) in the above example—as far as the compiler is concerned, b points at a BankAccount, and BankAccount objects do not have a buyStock method. Even in cases where it is “obvious” to a person looking at the code that the dynamic type will always be some more specific type, the compiler does not use this fact during type checking. 18.5 Method Overriding A child class may override a method it inherits from its parent class, specifying a new behavior for that method rather than using the one it inherits. In our BankAccount example, we might want to override the buyStock method in the MarginAccount class rather than use the method it would normally inherit from InvestmentAccount (to allow the account’s owner to buy stock without enough cash in the account). We might try to override the method by simply writing another method by the same name (and with the same parameter list) in the child class. In some OO languages (such as Java), this approach would work perfectly. However, in C++, this approach does not override the

behavior when we use instances of the class with the new behavior polymorphically. We will illustrate this concept with a simpler example: 1 #include <iostream> 2 #include <cstdlib> 3 4 class A { 5 public: 6 void sayHi() { 7 std::cout << \"Hello from class A\\n\"; 8} 9 }; 10 11 class B : public A { 12 public: 13 void sayHi() { 14 std::cout << \"Hello from class B\\n\"; 15 } 16 }; 17 18 int main(void) { 19 A anA; 20 B aB; 21 A * ptr = &aB; 22 anA.sayHi(); 23 aB.sayHi(); 24 ptr->sayHi(); 25 return EXIT_SUCCESS; 26 }; The output would be: Hello from class A Hello from class B Hello from class A

Figure 18.2: Depiction of the contents of main’s frame when we call the sayHi methods. Observe that the first two calls to the sayHi method have straightforward behavior: anA is an A, so it uses the sayHi method in class A, and aB is a B, so it uses the sayHi method in class B. However, when we do ptr- >sayHi(), we have slightly more interesting behavior. For ptr, the static type (recall: the type the compiler is aware of) of the object it points at is an A. We can see this fact just by looking at the code (which is really all the compiler sees)—ptr was declared as a pointer to an A, so that is the type the compiler knows. However, the dynamic type (recall: the type of object it is actually pointing at) is a B. To see this fact, we need to execute the code by hand. Doing so would yield a state as shown in Figure 18.2 when we reach the calls to the sayHi methods. Here, we can see that ptr is actually pointing at a B object. With the static and dynamic types being different, a natural question is “which one dictates what method to call?” As you can see from the output we gave earlier, the static type was used to determine the method to call—A’s sayHi was invoked for the ptr->sayHi call. The approach of having the static type determine which method to call is called static dispatch and is the default behavior in C++ unless we request otherwise. However, static dispatch disagrees with what we typically would want in the way we would use inheritance and polymorphism. Returning to our earlier example with MarginAccount’s buyStock method, we certainly want the method call to dispatch to the overridden method any time the owner attempts to buy stock.

The behavior we desire in this case (and most cases) is dynamic dispatch, in which the dynamic type of the object determines which method to invoke. If we want a method to be dynamically dispatched, we have to declare it as virtual. If we changed our simpler example to use dynamically dispatched methods, by declaring them virtual: 1 #include <iostream> 2 #include <cstdlib> 3 4 class A { 5 public: 6 virtual void sayHi() { //note the \"virtu 7 std::cout << \"Hello from class A\\n\"; 8} 9 }; 10 11 class B : public A { 12 public: 13 virtual void sayHi() { 14 std::cout << \"Hello from class B\\n\"; 15 } 16 }; 17 18 int main(void) { 19 A anA; 20 B aB; 21 A * ptr = &aB; 22 anA.sayHi(); 23 aB.sayHi(); 24 ptr->sayHi(); 25 return EXIT_SUCCESS; 26 }; The output would become: Hello from class A Hello from class B Hello from class B Notice how the last line has changed. Now, the call to sayHi is dynamically dispatched. When the static and

dynamic types are the same (the first two calls), this change does not make a difference in which method is called. However, when the static and dynamic types differ (as in the ptr->sayHi() call), the result changes. We now use dynamic dispatch, and call the method corresponding to the dynamic type of the object—what type of object the pointer actually points to. Note that the declaration of the method as virtual must appear in the parent class. The reason for this requirement is that when the compiler compiles the call to ptr->sayHi(), it only knows the static type of ptr. In this case, the static type of the object that ptr points to is an A, so the compiler looks in the definition of class A to see whether sayHi should be statically or dynamically dispatched. The compiler then generates different code based on whether the function is not virtual (in which case, it generates a direct call to A’s sayHi), or virtual (in which case, it generates code to dynamically dispatch the call, which is a bit more complex—we will discuss how this works in Chapter 29). Note that once a method is declared virtual, it remains virtual in all child classes (and their children, and so on), even if not explicitly declared so. However, it is good to explicitly declare them virtual to make the behavior clearer to anyone reading the code. Video 18.2: Method dispatch in C++. Having static dispatch as the default behavior is a design choice not seen in most OO languages other than C++. The motivation for this choice in C++ is performance. Dynamic dispatch has a slight runtime cost, and the designers of C++ decided that a programmer should only pay that cost if they need it. Most other OO languages decide to make dynamic dispatch the default

(or only) option, as it more naturally corresponds to what a programmer expects in an OO paradigm. Classes that contain virtual methods are never POD (plain old data) types, as they contain extra information to allow dynamic dispatch. For the rest of this book, we will draw objects with their type as part of their “box” if they contain virtual methods. While it may seem better to draw the type on all objects, only doing so when the object’s class contains at least one virtual method is the most accurate reflection of reality. In particular, an object with at least one virtual method has an extra field that is particular to its type (again, we’ll learn about the details in Chapter 29). Objects without virtual methods have no extra information and do not have an extra field, so it is a bit misleading to draw an extra “subbox” to indicate their type. Video 18.3: Dynamically dispatching methods during object construction and destruction. As you may recall from Section 18.3, in C++, the dynamic type of an object changes during object construction and destruction (in Java, the dynamic type remains constant). Now that we understand dynamic dispatch, we can see the reason such a rule may have meaningful impact on the behavior of our program. If we call virtual (dynamically dispatched) methods during the construction or destruction of an object (i.e., from its constructor or destructor), then the rules for what dynamic type an object has at each point in its construction (or destruction) govern what method is actually called. Video 18.3 illustrates. Video 18.4: The pitfalls of non-virtual destructors when objects are used polymorphically.

In C++, whenever you use a class that may participate in polymorphism, its destructor should be declared virtual. Declaring destructors as virtual whenever you use classes polymorphically is important to avoid issues with improperly destroying objects. When you delete an object, the destructor call is dispatched according to the same rules as method calls. If the destructor in the static type of the object being destroyed is not virtual, then the destructor call is statically dispatched. If the destructor in that class is virtual, then the destructor call is dynamically dispatched. Video 18.4 illustrates the pitfalls of a non-virtual destructor when objects are used polymorphically. There are a couple of other useful things to know about overriding methods. First, if you want to call the parent class’s version of a method, you can do so by explicitly requesting it with the fully qualified name. For example, if our MarginAccount class’s buyStock method wanted to call the inherited buyStock method (e.g., after borrowing money as needed), it could do so by using the fully qualified name of the method. For example, MarginAccount’s buyStock might have the following code: 1 class MarginAccount : public InvestmentAccount { 2 //other things here. 3 virtual bool buyStock(Stock * s, double numShar 4 double cost = getCost(s) * numShares; 5 double borrowAmount = 0; 6 if (balance < cost) { 7 borrowAmount = cost - balance; 8 if (marginUsed + borrowAmount < marginLimit 9 balance += borrowAmount; 10 marginUsed += borrowAmount; 11 } 12 else { 13 return false; 14 } 15 } 16 if (InvestmentAccount::buyStock(s, numShares 17 return true;

18 } 19 balance -= borrowAmount; 20 marginUsed -= borrowAmount; 21 return false; 22 } 23 }; Another useful thing to know is that an overridden method may have a more permissive access restriction (e.g., if the parent declares the method as private, the child could declare its overridden version as public). However, it cannot become more restrictive (you cannot override a public method with a private one). Additionally, an overridden method may change the return type in a covariant fashion—meaning that the return type in the subclass is a subtype of the return type in the superclass. For example: 1 class Animal { 2 public: 3 virtual Animal * getFather() { 4 //code here 5} 6 virtual Animal * getMother() { 7 //code here 8} 9 }; 10 class Cat : public Animal { 11 public: 12 virtual Cat * getFather() { 13 //code here 14 } 15 virtual Cat * getMother() { 16 //code here 17 } 18 }; Here, the Animal class has two methods (getFather and getMother), which each return an Animal *. This declaration makes sense, as an animal’s father or mother would be an animal. We then declare Cat as a subclass of Animal. Here we override these two methods but

change their return type to Cat *. This overriding is legal, as Cat * is a subtype of Animal *—that is, we can use polymorphism to assign a Cat * to an Animal *. This change of return type makes sense in this example, as the cat’s father and mother will be cats, not just any type of animal. Making the return type more specific in this fashion may be useful in code that uses the Cat class in a non-polymorphic fashion, as the compiler will know that the return value is a Cat * (allowing us to use methods specific to Cats). Note that if the methods returned Animal and Cat (by value, not by pointers), then this overriding would be illegal, as polymorphism only works on pointers or references. Attempting to do so would result in error messages such as: invalid covariant return type for ’virtual Cat Cat::getFather()’ overriding ’virtual Animal Animal::getFather()’ 18.6 Abstract Methods and Classes Suppose you were working on a program that dealt with shapes (e.g., some sort of graphics program). You might have a variety of classes for different shapes, such as a class for a Circle (which might have a radius and a center), a class for a Rectangle (which might have an , a , a width, and a height), and a class for a Triangle (which might have three points). Each of these classes exhibits an “is-a” relationship with a Shape class (a circle is a shape; a rectangle is a shape; a triangle is a shape), so we might want to make use of inheritance. We could declare a Shape class and then make Circle, Rectangle, and Triangle subclasses of Shape. Making use of inheritance would confer several advantages to our software design. We could make use

of polymorphism, allowing us to track all of the shapes in our system as an array of Shape *s. We could then make use of dynamic dispatch to have method invocations result in the correct code being executed based on the actual type of shape that was created. For example, we might have a containsPoint method, which takes a Point and tests if the point is inside of the shape. Each class could implement this method in the way appropriate to its own type of shape, and when we invoke containsPoint on a Shape *, dynamic dispatch would mean we call the right method for the type of shape we actually have. However, if we carefully scrutinize this design, we will notice something different from our previous examples. Although Shape is a perfectly valid class to make, there is nothing that is “just a shape” and not something more specific.1 Because there is nothing that is “just a shape,” we run into a small snag: we would like to declare the containsPoint method in the Shape class; however, there is no way to implement it. That is, we cannot write a correct containsPoint method in the Shape class because there is no correct way to do so. Writing a “dummy” implementation that always returns true (or always returns false) in unappealing, as it is an ugly hack, which is likely to lead to us forgetting to implement this method in a subclass and having an annoying error in our program. Instead, what we would like to do is declare the containsPoint method in the Shape class in such a way that we tell the compiler “there is no way I can define this method in this class, but any child class of mine must override this method with a real implementation.” Such a method is called an abstract method or a pure virtual member function.2 We declare a virtual method as abstract by placing = 0; at the end of its declaration:

1 class Shape { 2 public: 3 virtual bool containsPoint(const Point & p) con 4 }; Note that abstract methods must be virtual, as it only makes sense to use them with dynamic dispatch. The whole point is that we can have a Shape * (or Shape &), and call containsPoint on it without specifying how we would do containsPoint on a generic Shape. Now, each of our subclasses of Shape (Circle, Rectangle, and Triangle) will override the containsPoint method as appropriate to their respective types of shapes. When a class has an abstract method in it, that class becomes an abstract class. There are a few special rules that go along with abstract classes. The first is that an abstract class cannot be instantiated. That is, you cannot do new Shape, nor can you declare a variable to have type Shape. However, you can declare a variable (or parameter) to have type Shape * or Shape &. A Shape * or a Shape & can be used to polymorphically to reference an instance of a concrete subclass of Shape—one that has provided actual implementations for all of its abstract methods, such as Circle, Rectangle, or Triangle. The second rule is that any subclass of an abstract class is also abstract unless it defines concrete implementations for all abstract methods in its parents. If our design called for it, we could make an abstract subclass of Shape that does not define containsPoint and then make concrete subclasses of that class. Of course, we could also make a subclass that did define containsPoint but also declared new abstract methods of its own, and such a class would also be abstract. These two rules work together to make an important guarantee to the compiler. Any object you actually

instantiate will have an implementation for all of the methods declared in it. This rule is crucial to the usefulness of abstract classes. It means that whenever we have a Shape *, we can call containsPoint on it (or more generally, whenever we have a pointer or reference to an abstract class, we can call any of the methods that we have declared as abstract). Even though containsPoint is not implemented in Shape, the compiler can be assured that whatever subclass of Shape the pointer points at will have an implementation of this method. Recall that the compiler cannot figure out the dynamic type of the object that the pointer points at—it must only rely on the static type. Therefore, the Shape class must “promise” that any subclass has this method for the call to be legal. Video 18.5: Executing code with abstract classes. Video 18.5 illustrates the behavior of code with abstract classes. We will note that there is one “hole” in the guarantees made about having an implementation of the method available. Recall that in C++ (but not most other OO languages), the type of the object changes during the object construction (and destruction) process. As with any other class, abstract classes can have constructors, and the constructors for abstract parent classes are executed in the same way as the constructors for any other parent classes. If the code in the constructor of an abstract class is such that it calls an abstract method, there is a problem—the dynamic type of the object is the abstract class and no implementation is available. For example, if Shape had a constructor that called an abstract method, then during the Shape constructor, the dynamic type of the object being constructed is just

Shape. No implementation of the abstract method is available, so no legal call may be made. If the call appears directly in the constructor, the compiler will produce an error message. However, the compiler is easily fooled into not producing an error if the constructor calls some other method, which in turn calls the abstract method. In such a case, the program will crash when the abstract method is called while the dynamic type does not provide an implementation. Note that this issue is particular to the C++ rules, which change the object type during construction. In Java, the object type is immediately set at the type being constructed, and the method call is dynamically dispatched to that type’s implementation. 18.7 Inheritance and Templates As we discussed earlier, most features we see in programming languages are composable—we can mix them together, and they work exactly the way we would expect. For example, function parameters and references exhibit this property. If we know how to declare a function parameter and we know how to declare a reference, we can combine the two and declare a function parameter that is a reference—and it works exactly the way we expect. Unlike most pairs of features, templates are not fully composable with inheritance, mostly with respect to the rules that relate to virtual methods. While this delves a little more into odd corners of the language than we typically like to go, we mention it to help you avoid surprises (and the frustration that goes along with them). 18.7.1 Aspects That Are Composable First, we will start with some aspects that are composable. It is perfectly fine (and works “as expected”)

to have a templated class inherit from another class, to inherit from an instantiation of a templated class, or to mix the two (having a templated class inherit from an instantiation of another templated class). Often when we want to have a templated class inherit from a templated parent class, we want to keep the generality of the parent class—we can achieve this behavior by instantiating the parent class with the template parameter of the child: 1 template<typename T> 2 class MyFancyVector : public std::vector<T> { 3 //whatever we want 4 }; Here we are still instantiating std::vector, we just happen to be instantiating it with T, which is the template parameter of MyFancyVector. Whenever we instantiate MyFancyVector, the resulting class will inherit from std::vector instantiated with the same argument as MyFancyVector (that is, MyFancyVector<int> will inherit from std::vector<int>. We will note that you can even parameterize a class in terms of what its parent is: 1 template<typename T> 2 class MyClass : public T { 3 //code here... 4} This design is called a mixin, and we will discuss it in more detail in Chapter 29. It is also perfectly fine for a templated class to have virtual methods: 1 template<typename T> 2 class MyClass { 3 public: 4 //perfectly fine 5 virtual int computeSomething(int x) {

6 //some code 7} 8 //also fine 9 virtual void someFunction() = 0; 10 //still fine, good idea if used polymorphically 11 virtual ~MyClass() {} 12 }; 18.7.2 Aspects That Are Not Composable Generally speaking, virtual methods and templates interact in complex ways. Understanding why these interactions occur requires understanding the material in Chapter 29, so we will not cover it now. Instead, we will just give you some rules to be wary of: A templated method cannot be virtual. You cannot declare a templated method to be virtual (do not confuse this with a method inside of a templated class, which can be virtual as we discussed above): 1 class MyClass { 2 public: 3 //illegal: virtual templated function 4 template<typename X> virtual 5 int doSomething(const X & arg) { 6 //some code here... 7} 8 }; Attempting to do so will result in an error message such as: error: templates may not be ’virtual’ template<typename X> virtual If you want to have a variety of virtual methods with similar functionality in the base class, you can instead make a protected (or

private) non-virtual template, and have non- templated methods call it: 1 class MyClass { 2 protected: 3 //templated, non-virtual: legal 4 template<typename X> 5 int doSomething_implementation(const 6 //code here. 7} 8 public: 9 //virtual, non-templated: legal 10 virtual int doSomething(const int & 11 return doSomething_implementation< 12 } 13 //virtual, non-templated: legal 14 virtual int doSomething(const double 15 return doSomething_implementation< 16 } 17 //etc. 18 }; A templated function cannot override an inherited method. Suppose we have a parent class: 1 class Parent { 2 public: 3 virtual void something() { 4 std::cout << \"Parent::something\\n\" 5} 6 }; Now, suppose we write a child class with a method by the same name (and parameter list—whether or not the parameter list is the same due to template specialization or not): 1 class Child : public Parent { 2 public: 3 template<typename T> void something( 4 std::cout << \"Child::something<T>\\ 5}

6 }; This Child class does not actually override the method of the same name from the Parent class. Instead, we have a non- virtual template method in the child class and inherit the virtual method from the parent class. If we execute the following code: 1 Parent * p = new Child(); 2 p->something(); then it will print \"Parent::something\\n\". This rule is something of a corollary of the previous rule, as the method would have to be virtual to override a virtual method—however, the language designers decided to make this method legal as a non-virtual method that does not override the parent, rather than illegal under the previous rule. Virtual methods are specialized when an instance is made. In Section 17.3.3 we discussed how a templated function, class, or method is only type checked when it is specialized. In that section, we also discussed how one may specialize a class, and create instances of the specialized class, without the compiler specializing (and thus type checking) all of the methods inside of that templated class. However, this rule only applies to non-virtual methods. If a method is virtual, then the compiler must specialize (and thus type check) it whenever it must create an instance of the class.

18.8 Planning Your Inheritance Hierarchy So far, we have covered a lot of important concepts in terms of what inheritance is and how it works. However, as with most programming tools, you need to know how to use inheritance properly in order for it to work well for you. As always, planning is the key to using it well. Here is a good general, high-level approach to planning your inheritance relationships: 1. Determine what classes you need and what members they have. 2. Look for similarities between classes: do multiple classes have the same members (even if the methods would behave differently)? If you find similarities, determine if you can pull the similar members out into another class that exhibits an “is-a” relationship with the classes in question. If so, this new class represents a strong possibility for a class that is a parent of the other classes in question. 3. Look to see if there are anything with natural “is-a” relationships that are not related by inheritance. If so, consider making one a subclass of the other. Contemplate what behaviors and fields can be moved into the parent classes. 4. Repeat steps 2 and 3 until you run out of opportunities for good uses of inheritance. 5. Determine which classes should be abstract. These are the classes you cannot actually have “just” that type of thing without being more specific. Typically this constraint goes hand-in-hand with having a method where all things of that type are certain to have that method, but you cannot define it for the type in question.

There are a few other general guidelines to think about in designing your inheritance relationships: • In general, you want a common member as far “up” the inheritance hierarchy as possible (meaning in a parent class rather than a child class). Doing so avoids duplicating code. Of course, you should only put the field or method in the parent class if the parent type actually has that field or (possibly abstract) method. • Make plentiful use of dynamic dispatch. Good object-oriented programming favors letting dynamic dispatch “make the decision” of what to do based on what type of object you have over explicit conditionals (e.g., if/else). Figure 18.3: Naïve class relationships for our hypothetical game. As an example of designing our inheritance hierarchy, let us suppose we were writing some sort of game. In this hypothetical game, there is a hero (controlled by the player), who has some hit points (“life”), magic points, and can gain levels (become stronger). The hero has some position on the screen, which is a point, and a collection of images (so she can be drawn in a


Like this book? You can publish your book online for free in a few minutes!
Create your own flipbook