Chapter 15. Object-Oriented Programming

Size: px
Start display at page:

Download "Chapter 15. Object-Oriented Programming"

Transcription

1 number of operands as their built-in counterparts. user-defined conversion A synonym for class-type conversion. Chapter 15. Object-Oriented Programming Contents Section 15.1 OOP: An Overview Section 15.2 Defining Base and Derived Classes Section 15.3 Virtual Functions Section 15.4 Abstract Base Classes Section 15.5 Access Control and Inheritance Section 15.6 Class Scope under Inheritance Section 15.7 Constructors and Copy Control Section 15.8 Containers and Inheritance Section 15.9 Text Queries Revisited Chapter Summary Defined Terms Object-oriented programming is based on three fundamental concepts: data abstraction, which we covered in Chapter 7, and inheritance and dynamic binding, which we ll cover in this chapter. Inheritance and dynamic binding affect how we write our programs in two ways: They make it easier to define new classes that are similar, but not identical, to other classes, and they make it easier for us to write programs that can ignore the details of how those similar types differ. Many applications include concepts that are related to but slightly different from one another. For example, our bookstore might offer different pricing strategies for different books. Some books might be sold only at a given price. Others might be sold subject to a discount. We might give a discount to purchasers who buy a specified number of copies of the book. Or we might give a discount for only the first few copies purchased but charge full price for any bought beyond a given limit, and so on. Object-oriented programming (OOP) is a good match to this kind of application OOP: An Overview

2 The key ideas in object-oriented programming are data abstraction, inheritance, and dynamic binding. Using data abstraction, we can define classes that separate interface from implementation (Chapter 7). Through inheritance, we can define classes that model the relationships among similar types. Through dynamic binding, we can use objects of these types while ignoring the details of how they differ. Inheritance Classes related by inheritance form a hierarchy. Typically there is a base class at the root of the hierarchy, from which the other classes inherit, directly or indirectly. These inheriting classes are known as derived classes. The base class defines those members that are common to the types in the hierarchy. Each derived class defines those members that are specific to the derived class itself. To model our different kinds of pricing strategies, we ll define a class named Quote, which will be the base class of our hierarchy. A Quote object will represent undiscounted books. From Quote we will inherit a second class, named Bulk_quote, to represent books that can be sold with a quantity discount. These classes will have the following two member functions: isbn(), which will return the ISBN. This operation does not depend on the specifics of the inherited class(es); it will be defined only in class Quote. net_price(size_t), which will return the price for purchasing a specified number of copies of a book. This operation is type specific; both Quote and Bulk_quote will define their own version of this function. In C++, a base class distinguishes functions that are type dependent from those that it expects its derived classes to inherit without change. The base class defines as virtual those functions it expects its derived classes to define for themselves. Using this knowledge, we can start to write our Quote class: class Quote { public: std::string isbn() const; virtual double net_price(std::size_t n) const; A derived class must specify the class(es) from which it intends to inherit. It does so in a class derivation list, which is a colon followed by a comma-separated list of base classes each of which may have an optional access specifier: class Bulk_quote : public Quote { // Bulk_quote inherits from Quote public: double net_price(std::size_t) const override;

3 Because Bulk_quote uses public in its derivation list, we can use objects of type Bulk_quote as if they were Quote objects. A derived class must include in its own class body a declaration of all the virtual functions it intends to define for itself. A derived class may include the virtual keyword on these functions but is not required to do so. For reasons we ll explain in 15.3 (p. 606), the new standard lets a derived class explicitly note that it intends a member function to override a virtual that it inherits. It does so by specifying override after its parameter list. Dynamic Binding Through dynamic binding, we can use the same code to process objects of either type Quote or Bulk_quote interchangeably. For example, the following function prints the total price for purchasing the given number of copies of a given book: // calculate and print the price for the given number of copies, applying any discounts double print_total(ostream &os, const Quote &item, size_t n) { // depending on the type of the object bound to the item parameter // calls either Quote::net_price or Bulk_quote::net_price double ret = item.net_price(n); os << "ISBN: " << item.isbn() // calls Quote::isbn << " # sold: " << n << " total due: " << ret << endl; return ret; } This function is pretty simple it prints the results of calling isbn and net_price on its parameter and returns the value calculated by the call to net_price. Nevertheless, there are two interesting things about this function: For reasons we ll explain in (p. 601), because the item parameter is a reference to Quote, we can call this function on either a Quote object or a Bulk_quote object. And, for reasons we ll explain in (p. 594), because net_price is a virtual function, and because print_total calls net_price through a reference, the version of net_price that is run will depend on the type of the object that we pass to print_total: // basic has type Quote; bulk has type Bulk_quote print_total(cout, basic, 20); // calls Quote version of net_price print_total(cout, bulk, 20); // calls Bulk_quote version of net_price The first call passes a Quote object to print_total. When print_total calls net_price, the Quote version will be run. In the next call, the argument is a

4 Bulk_quote, so the Bulk_quote version of net_price (which applies a discount) will be run. Because the decision as to which version to run depends on the type of the argument, that decision can t be made until run time. Therefore, dynamic binding is sometimes known as run-time binding. Note In C++, dynamic binding happens when a virtual function is called through a reference (or a pointer) to a base class Defining Base and Derived Classes In many, but not all, ways base and derived classes are defined like other classes we have already seen. In this section, we ll cover the basic features used to define classes related by inheritance Defining a Base Class We ll start by completing the definition of our Quote class: class Quote { public: Quote() = default; // = default see (p. 264) Quote(const std::string &book, double sales_price): bookno(book), price(sales_price) { } std::string isbn() const { return bookno; } // returns the total sales price for the specified number of items // derived classes will override and apply different discount algorithms virtual double net_price(std::size_t n) const { return n * price; } virtual ~Quote() = default; // dynamic binding for the destructor private: std::string bookno; // ISBN number of this item protected: double price = 0.0; // normal, undiscounted price The new parts in this class are the use of virtual on the net_price function and the destructor, and the protected access specifier. We ll explain virtual destructors in (p. 622), but for now it is worth noting that classes used as the root of an inheritance hierarchy almost always define a virtual destructor.

5 Note Base classes ordinarily should define a virtual destructor. Virtual destructors are needed even if they do no work. Member Functions and Inheritance Derived classes inherit the members of their base class. However, a derived class needs to be able to provide its own definition for operations, such as net_price, that are type dependent. In such cases, the derived class needs to override the definition it inherits from the base class, by providing its own definition. In C++, a base class must distinguish the functions it expects its derived classes to override from those that it expects its derived classes to inherit without change. The base class defines as virtual those functions it expects its derived classes to override. When we call a virtual function through a pointer or reference, the call will be dynamically bound. Depending on the type of the object to which the reference or pointer is bound, the version in the base class or in one of its derived classes will be executed. A base class specifies that a member function should be dynamically bound by preceding its declaration with the keyword virtual. Any nonstatic member function ( 7.6, p. 300), other than a constructor, may be virtual. The virtual keyword appears only on the declaration inside the class and may not be used on a function definition that appears outside the class body. A function that is declared as virtual in the base class is implicitly virtual in the derived classes as well. We ll have more to say about virtual functions in 15.3 (p. 603). Member functions that are not declared as virtual are resolved at compile time, not run time. For the isbn member, this is exactly the behavior we want. The isbn function does not depend on the details of a derived type. It behaves identically when run on a Quote or Bulk_quote object. There will be only one version of the isbn function in our inheritance hierarchy. Thus, there is no question as to which function to run when we call isbn(). Access Control and Inheritance A derived class inherits the members defined in its base class. However, the member functions in a derived class may not necessarily access the members that are inherited from the base class. Like any other code that uses the base class, a derived class may access the public members of its base class but may not access the private members. However, sometimes a base class has members that it wants to let its derived classes use while still prohibiting access to those same members by other

6 users. We specify such members after a protected access specifier. Our Quote class expects its derived classes to define their own net_price function. To do so, those classes need access to the price member. As a result, Quote defines that member as protected. Derived classes are expected to access bookno in the same way as ordinary users by calling the isbn function. Hence, the bookno member is private and is inaccessible to classes that inherit from Quote. We ll have more to say about protected members in 15.5 (p. 611). Exercises Section Exercise 15.1: What is a virtual member? Exercise 15.2: How does the protected access specifier differ from private? Exercise 15.3: Define your own versions of the Quote class and the print_total function Defining a Derived Class A derived class must specify from which class(es) it inherits. It does so in its class derivation list, which is a colon followed by a comma-separated list of names of previously defined classes. Each base class name may be preceded by an optional access specifier, which is one of public, protected, or private. A derived class must declare each inherited member function it intends to override. Therefore, our Bulk_quote class must include a net_price member: class Bulk_quote : public Quote { // Bulk_quote inherits from Quote Bulk_quote() = default; Bulk_quote(const std::string&, double, std::size_t, double); // overrides the base version in order to implement the bulk purchase discount policy double net_price(std::size_t) const override; private: std::size_t min_qty = 0; // minimum purchase for the discount to apply double discount = 0.0; // fractional discount to apply Our Bulk_quote class inherits the isbn function and the bookno and price data members of its Quote base class. It defines its own version of net_price and has

7 two additional data members, min_qty and discount. These members specify the minimum quantity and the discount to apply once that number of copies are purchased. We ll have more to say about the access specifier used in a derivation list in 15.5 (p. 612). For now, what s useful to know is that the access specifier determines whether users of a derived class are allowed to know that the derived class inherits from its base class. When the derivation is public, the public members of the base class become part of the interface of the derived class as well. In addition, we can bind an object of a publicly derived type to a pointer or reference to the base type. Because we used public in the derivation list, the interface to Bulk_quote implicitly contains the isbn function, and we may use a Bulk_quote object where a pointer or reference to Quote is expected. Most classes inherit directly from only one base class. This form of inheritance, known as single inheritance, forms the topic of this chapter (p. 802) will cover classes that have derivation lists with more than one base class. Virtual Functions in the Derived Class Derived classes frequently, but not always, override the virtual functions that they inherit. If a derived class does not override a virtual from its base, then, like any other member, the derived class inherits the version defined in its base class. A derived class may include the virtual keyword on the functions it overrides, but it is not required to do so. For reasons we ll explain in 15.3 (p. 606), the new standard lets a derived class explicitly note that it intends a member function to override a virtual that it inherits. It does so by specifying override after the parameter list, or after the const or reference qualifier(s) if the member is a const ( 7.1.2, p. 258) or reference ( , p. 546) function. Derived-Class Objects and the Derived-to-Base Conversion A derived object contains multiple parts: a subobject containing the (nonstatic) members defined in the derived class itself, plus subobjects corresponding to each base class from which the derived class inherits. Thus, a Bulk_quote object will contain four data elements: the bookno and price data members that it inherits from Quote, and the min_qty and discount members, which are defined by Bulk_quote. Although the standard does not specify how derived objects are laid out in memory, we can think of a Bulk_quote object as consisting of two parts as represented in Figure 15.1.

8 Figure Conceptual Structure of a Bulk_quote Object The base and derived parts of an object are not guaranteed to be stored contiguously. Figure 15.1 is a conceptual, not physical, representation of how classes work. Because a derived object contains subparts corresponding to its base class(es), we can use an object of a derived type as if it were an object of its base type(s). In particular, we can bind a base-class reference or pointer to the base-class part of a derived object. Quote item; // object of base type Bulk_quote bulk; // object of derived type Quote *p = &item; // p points to a Quote object p = &bulk; // p points to the Quote part of bulk Quote &r = bulk; // r bound to the Quote part of bulk This conversion is often referred to as the derived-to-base conversion. As with any other conversion, the compiler will apply the derived-to-base conversion implicitly ( 4.11, p. 159). The fact that the derived-to-base conversion is implicit means that we can use an object of derived type or a reference to a derived type when a reference to the base type is required. Similarly, we can use a pointer to a derived type where a pointer to the base type is required. Note The fact that a derived object contains subobjects for its base classes is key to how inheritance works. Derived-Class Constructors Although a derived object contains members that it inherits from its base, it cannot

9 directly initialize those members. Like any other code that creates an object of the base-class type, a derived class must use a base-class constructor to initialize its base-class part. Note Each class controls how its members are initialized. The base-class part of an object is initialized, along with the data members of the derived class, during the initialization phase of the constructor ( 7.5.1, p. 288). Analogously to how we initialize a member, a derived-class constructor uses its constructor initializer list to pass arguments to a base-class constructor. For example, the Bulk_quote constructor with four parameters: Bulk_quote(const std::string& book, double p, std::size_t qty, double disc) : Quote(book, p), min_qty(qty), discount(disc) { } // as before passes its first two parameters (representing the ISBN and price) to the Quote constructor. That Quote constructor initializes the Bulk_quote s base-class part (i.e., the bookno and price members). When the (empty) Quote constructor body completes, the base-class part of the object being constructed will have been initialized. Next the direct members, min_qty and discount, are initialized. Finally, the (empty) function body of the Bulk_quote constructor is run. As with a data member, unless we say otherwise, the base part of a derived object is default initialized. To use a different base-class constructor, we provide a constructor initializer using the name of the base class, followed (as usual) by a parenthesized list of arguments. Those arguments are used to select which base-class constructor to use to initialize the base-class part of the derived object. Note The base class is initialized first, and then the members of the derived class are initialized in the order in which they are declared in the class. Using Members of the Base Class from the Derived Class A derived class may access the public and protected members of its base class:

10 // if the specified number of items are purchased, use the discounted price double Bulk_quote::net_price(size_t cnt) const { if (cnt >= min_qty) return cnt * (1 - discount) * price; else return cnt * price; } This function generates a discounted price: If the given quantity is more than min_qty, we apply the discount (which was stored as a fraction) to the price. We ll have more to say about scope in 15.6 (p. 617), but for now it s worth knowing that the scope of a derived class is nested inside the scope of its base class. As a result, there is no distinction between how a member of the derived class uses members defined in its own class (e.g., min_qty and discount) and how it uses members defined in its base (e.g., price). Key Concept: Respecting the Base-Class Interface It is essential to understand that each class defines its own interface. Interactions with an object of a class-type should use the interface of that class, even if that object is the base-class part of a derived object. As a result, derived-class constructors may not directly initialize the members of its base class. The constructor body of a derived constructor can assign values to its public or protected base-class members. Although it can assign to those members, it generally should not do so. Like any other user of the base class, a derived class should respect the interface of its base class by using a constructor to initialize its inherited members. Inheritance and static Members If a base class defines a static member ( 7.6, p. 300), there is only one such member defined for the entire hierarchy. Regardless of the number of classes derived from a base class, there exists a single instance of each static member. class Base { public: static void statmem(); class Derived : public Base { void f(const Derived&);

11 static members obey normal access control. If the member is private in the base class, then derived classes have no access to it. Assuming the member is accessible, we can use a static member through either the base or derived: void Derived::f(const Derived &derived_obj) { Base::statmem(); // ok: Base defines statmem Derived::statmem(); // ok: Derived inherits statmem // ok: derived objects can be used to access static from base derived_obj.statmem(); // accessed through a Derived object statmem(); // accessed through this object } Declarations of Derived Classes A derived class is declared like any other class ( 7.3.3, p. 278). The declaration contains the class name but does not include its derivation list: class Bulk_quote : public Quote; // error: derivation list can't appear here class Bulk_quote; // ok: right way to declare a derived class The purpose of a declaration is to make known that a name exists and what kind of entity it denotes, for example, a class, function, or variable. The derivation list, and all other details of the definition, must appear together in the class body. Classes Used as a Base Class A class must be defined, not just declared, before we can use it as a base class: class Quote; // declared but not defined // error: Quote must be defined class Bulk_quote : public Quote {... The reason for this restriction should be easy to see: Each derived class contains, and may use, the members it inherits from its base class. To use those members, the derived class must know what they are. One implication of this rule is that it is impossible to derive a class from itself. A base class can itself be a derived class:

12 class Base { /*... */ } ; class D1: public Base { /*... */ class D2: public D1 { /*... */ In this hierarchy, Base is a direct base to D1 and an indirect base to D2. A direct base class is named in the derivation list. An indirect base is one that a derived class inherits through its direct base class. Each class inherits all the members of its direct base class. The most derived class inherits the members of its direct base. The members in the direct base include those it inherits from its base class, and so on up the inheritance chain. Effectively, the most derived object contains a subobject for its direct base and for each of its indirect bases. Preventing Inheritance Sometimes we define a class that we don t want others to inherit from. Or we might define a class for which we don t want to think about whether it is appropriate as a base class. Under the new standard, we can prevent a class from being used as a base by following the class name with final: class NoDerived final { /* */ // NoDerived can't be a base class class Base { /* */ // Last is final; we cannot inherit from Last class Last final : Base { /* */ // Last can't be a base class class Bad : NoDerived { /* */ // error: NoDerived is final class Bad2 : Last { /* */ // error: Last is final Exercises Section Exercise 15.4: Which of the following declarations, if any, are incorrect? Explain why. class Base {... (a) class Derived : public Derived {... (b) class Derived : private Base {... (c) class Derived : public Base; Exercise 15.5: Define your own version of the Bulk_quote class. Exercise 15.6: Test your print_total function from the exercises in (p. 595) by passing both Quote and Bulk_quote objects o that function.

13 Exercise 15.7: Define a class that implements a limited discount strategy, which applies a discount to books purchased up to a given limit. If the number of copies exceeds that limit, the normal price applies to those purchased beyond the limit Conversions and Inheritance Warning Understanding conversions between base and derived classes is essential to understanding how object-oriented programming works in C++. Ordinarily, we can bind a reference or a pointer only to an object that has the same type as the corresponding reference or pointer ( 2.3.1, p. 51, and 2.3.2, p. 52) or to a type that involves an acceptable const conversion ( , p. 162). Classes related by inheritance are an important exception: We can bind a pointer or reference to a base-class type to an object of a type derived from that base class. For example, we can use a Quote& to refer to a Bulk_quote object, and we can assign the address of a Bulk_quote object to a Quote*. The fact that we can bind a reference (or pointer) to a base-class type to a derived object has a crucially important implication: When we use a reference (or pointer) to a base-class type, we don t know the actual type of the object to which the pointer or reference is bound. That object can be an object of the base class or it can be an object of a derived class. Note Like built-in pointers, the smart pointer classes ( 12.1, p. 450) support the derived-to-base conversion we can store a pointer to a derived object in a smart pointer to the base type. Static Type and Dynamic Type When we use types related by inheritance, we often need to distinguish between the static type of a variable or other expression and the dynamic type of the object that expression represents. The static type of an expression is always known at

14 compile time it is the type with which a variable is declared or that an expression yields. The dynamic type is the type of the object in memory that the variable or expression represents. The dynamic type may not be known until run time. For example, when print_total calls net_price ( 15.1, p. 593): double ret = item.net_price(n); we know that the static type of item is Quote&. The dynamic type depends on the type of the argument to which item is bound. That type cannot be known until a call is executed at run time. If we pass a Bulk_quote object to print_total, then the static type of item will differ from its dynamic type. As we ve seen, the static type of item is Quote&, but in this case the dynamic type is Bulk_quote. The dynamic type of an expression that is neither a reference nor a pointer is always the same as that expression s static type. For example, a variable of type Quote is always a Quote object; there is nothing we can do that will change the type of the object to which that variable corresponds. Note It is crucial to understand that the static type of a pointer or reference to a base class may differ from its dynamic type. There Is No Implicit Conversion from Base to Derived... The conversion from derived to base exists because every derived object contains a base-class part to which a pointer or reference of the base-class type can be bound. There is no similar guarantee for base-class objects. A base-class object can exist either as an independent object or as part of a derived object. A base object that is not part of a derived object has only the members defined by the base class; it doesn t have the members defined by the derived class. Because a base object might or might not be part of a derived object, there is no automatic conversion from the base class to its derived class(s): Quote base; Bulk_quote* bulkp = &base; // error: can't convert base to derived Bulk_quote& bulkref = base; // error: can't convert base to derived If these assignments were legal, we might attempt to use bulkp or bulkref to use members that do not exist in base. What is sometimes a bit surprising is that we cannot convert from base to derived even when a base pointer or reference is bound to a derived object:

15 Bulk_quote bulk; Quote *itemp = &bulk; Bulk_quote *bulkp = itemp; // ok: dynamic type is Bulk_quote // error: can't convert base to derived The compiler has no way to know (at compile time) that a specific conversion will be safe at run time. The compiler looks only at the static types of the pointer or reference to determine whether a conversion is legal. If the base class has one or more virtual functions, we can use a dynamic_cast (which we ll cover in (p. 825)) to request a conversion that is checked at run time. Alternatively, in those cases when we know that the conversion from base to derived is safe, we can use a static_cast ( , p. 162) to override the compiler....and No Conversion between Objects The automatic derived-to-base conversion applies only for conversions to a reference or pointer type. There is no such conversion from a derived-class type to the baseclass type. Nevertheless, it is often possible to convert an object of a derived class to its base-class type. However, such conversions may not behave as we might want. Remember that when we initialize or assign an object of a class type, we are actually calling a function. When we initialize, we re calling a constructor ( , p. 496, and , p. 534); when we assign, we re calling an assignment operator ( , p. 500, and , p. 536). These members normally have a parameter that is a reference to the const version of the class type. Because these members take references, the derived-to-base conversion lets us pass a derived object to a base-class copy/move operation. These operations are not virtual. When we pass a derived object to a base-class constructor, the constructor that is run is defined in the base class. That constructor knows only about the members of the base class itself. Similarly, if we assign a derived object to a base object, the assignment operator that is run is the one defined in the base class. That operator also knows only about the members of the base class itself. For example, our bookstore classes use the synthesized versions of copy and assignment ( , p. 497, and , p. 500). We ll have more to say about copy control and inheritance in (p. 623), but for now what s useful to know is that the synthesized versions memberwise copy or assign the data members of the class the same way as for any other class: Bulk_quote bulk; // object of derived type Quote item(bulk); // uses the Quote::Quote(const Quote&) constructor item = bulk; // calls Quote::operator=(const Quote&)

16 When item is constructed, the Quote copy constructor is run. That constructor knows only about the bookno and price members. It copies those members from the Quote part of bulk and ignores the members that are part of the Bulk_quote portion of bulk. Similarly for the assignment of bulk to item; only the Quote part of bulk is assigned to item. Because the Bulk_quote part is ignored, we say that the Bulk_quote portion of bulk is sliced down. Warning When we initialize or assign an object of a base type from an object of a derived type, only the base-class part of the derived object is copied, moved, or assigned. The derived part of the object is ignored Virtual Functions As we ve seen, in C++ dynamic binding happens when a virtual member function is called through a reference or a pointer to a base-class type ( 15.1, p. 593). Because we don t know which version of a function is called until run time, virtual functions must always be defined. Ordinarily, if we do not use a function, we don t need to supply a definition for that function ( 6.1.2, p. 206). However, we must define every virtual function, regardless of whether it is used, because the compiler has no way to determine whether a virtual function is used. Exercises Section Exercise 15.8: Define static type and dynamic type. Exercise 15.9: When is it possible for an expression s static type to differ from its dynamic type? Give three examples in which the static and dynamic type differ. Exercise 15.10: Recalling the discussion from 8.1 (p. 311), explain how the program on page 317 that passed an ifstream to the Sales_data read function works. Key Concept: Conversions among Types Related by Inheritance There are three things that are important to understand about conversions among classes related by inheritance: The conversion from derived to base applies only to pointer or reference

17 types. There is no implicit conversion from the base-class type to the derived type. Like any member, the derived-to-base conversion may be inaccessible due to access controls. We ll cover accessibility in 15.5 (p. 613). Although the automatic conversion applies only to pointers and references, most classes in an inheritance hierarchy (implicitly or explicitly) define the copy-control members (Chapter 13). As a result, we can often copy, move, or assign an object of derived type to a base-type object. However, copying, moving, or assigning a derived-type object to a base-type object copies, moves, or assigns only the members in the base-class part of the object. Calls to Virtual Functions May Be Resolved at Run Time When a virtual function is called through a reference or pointer, the compiler generates code to decide at run time which function to call. The function that is called is the one that corresponds to the dynamic type of the object bound to that pointer or reference. As an example, consider our print_total function from 15.1 (p. 593). That function calls net_price on its parameter named item, which has type Quote&. Because item is a reference, and because net_price is virtual, the version of net_price that is called depends at run time on the actual (dynamic) type of the argument bound to item: Quote base(" ", 50); print_total(cout, base, 10); // calls Quote::net_price Bulk_quote derived(" ", 50, 5,.19); print_total(cout, derived, 10); // calls Bulk_quote::net_price In the first call, item is bound to an object of type Quote. As a result, when print_total calls net_price, the version defined by Quote is run. In the second call, item is bound to a Bulk_quote object. In this call, print_total calls the Bulk_quote version of net_price. It is crucial to understand that dynamic binding happens only when a virtual function is called through a pointer or a reference. base = derived; base.net_price(20); // copies the Quote part of derived into base // calls Quote::net_price When we call a virtual function on an expression that has a plain nonreference and

18 nonpointer type, that call is bound at compile time. For example, when we call net_price on base, there is no question as to which version of net_price to run. We can change the value (i.e., the contents) of the object that base represents, but there is no way to change the type of that object. Hence, this call is resolved, at compile time, to the Quote version of net_price. Key Concept: Polymorphism in C++ The key idea behind OOP is polymorphism. Polymorphism is derived from a Greek word meaning many forms. We speak of types related by inheritance as polymorphic types, because we can use the many forms of these types while ignoring the differences among them. The fact that the static and dynamic types of references and pointers can differ is the cornerstone of how C++ supports polymorphism. When we call a function defined in a base class through a reference or pointer to the base class, we do not know the type of the object on which that member is executed. The object can be a base-class object or an object of a derived class. If the function is virtual, then the decision as to which function to run is delayed until run time. The version of the virtual function that is run is the one defined by the type of the object to which the reference is bound or to which the pointer points. On the other hand, calls to nonvirtual functions are bound at compile time. Similarly, calls to any function (virtual or not) on an object are also bound at compile time. The type of an object is fixed and unvarying there is nothing we can do to make the dynamic type of an object differ from its static type. Therefore, calls made on an object are bound at compile time to the version defined by the type of the object. Note Virtuals are resolved at run time only if the call is made through a reference or pointer. Only in these cases is it possible for an object s dynamic type to differ from its static type. Virtual Functions in a Derived Class When a derived class overrides a virtual function, it may, but is not required to, repeat the virtual keyword. Once a function is declared as virtual, it remains virtual in all the derived classes. A derived-class function that overrides an inherited virtual function must have

19 exactly the same parameter type(s) as the base-class function that it overrides. With one exception, the return type of a virtual in the derived class also must match the return type of the function from the base class. The exception applies to virtuals that return a reference (or pointer) to types that are themselves related by inheritance. That is, if D is derived from B, then a base class virtual can return a B* and the version in the derived can return a D*. However, such return types require that the derived-to-base conversion from D to B is accessible (p. 613) covers how to determine whether a base class is accessible. We ll see an example of this kind of virtual function in (p. 633). Note A function that is virtual in a base class is implicitly virtual in its derived classes. When a derived class overrides a virtual, the parameters in the base and derived classes must match exactly. The final and override Specifiers As we ll see in 15.6 (p. 620), it is legal for a derived class to define a function with the same name as a virtual in its base class but with a different parameter list. The compiler considers such a function to be independent from the base-class function. In such cases, the derived version does not override the version in the base class. In practice, such declarations often are a mistake the class author intended to override a virtual from the base class but made a mistake in specifying the parameter list. Finding such bugs can be surprisingly hard. Under the new standard we can specify override on a virtual function in a derived class. Doing so makes our intention clear and (more importantly) enlists the compiler in finding such problems for us. The compiler will reject a program if a function marked override does not override an existing virtual function: struct B { virtual void f1(int) const; virtual void f2(); void f3(); struct D1 : B { void f1(int) const override; // ok: f1 matches f1 in the base void f2(int) override; // error: B has no f2(int) function void f3() override; // error: f3 not virtual void f4() override; // error: B doesn't have a function named f4

20 In D1, the override specifier on f1 is fine; both the base and derived versions of f1 are const members that take an int and return void. The version of f1 in D1 properly overrides the virtual that it inherits from B. The declaration of f2 in D1 does not match the declaration of f2 in B the version defined in B takes no arguments and the one defined in D1 takes an int. Because the declarations don t match, f2 in D1 doesn t override f2 from B; it is a new function that happens to have the same name. Because we said we intended this declaration to be an override and it isn t, the compiler will generate an error. Because only a virtual function can be overridden, the compiler will also reject f3 in D1. That function is not virtual in B, so there is no function to override. Similarly f4 is in error because B doesn t even have a function named f4. We can also designate a function as final. Any attempt to override a function that has been defined as final will be flagged as an error: struct D2 : B { // inherits f2() and f3() from B and overrides f1(int) void f1(int) const final; // subsequent classes can't override f1 (int) struct D3 : D2 { void f2(); // ok: overrides f2 inherited from the indirect base, B void f1(int) const; // error: D2 declared f2 as final final and override specifiers appear after the parameter list (including any const or reference qualifiers) and after a trailing return ( 6.3.3, p. 229). Virtual Functions and Default Arguments Like any other function, a virtual function can have default arguments ( 6.5.1, p. 236). If a call uses a default argument, the value that is used is the one defined by the static type through which the function is called. That is, when a call is made through a reference or pointer to base, the default argument(s) will be those defined in the base class. The base-class arguments will be used even when the derived version of the function is run. In this case, the derived function will be passed the default arguments defined for the base-class version of the function. If the derived function relies on being passed different arguments, the program will not execute as expected. Best Practices

21 Virtual functions that have default arguments should use the same argument values in the base and derived classes. Circumventing the Virtual Mechanism In some cases, we want to prevent dynamic binding of a call to a virtual function; we want to force the call to use a particular version of that virtual. We can use the scope operator to do so. For example, this code: // calls the version from the base class regardless of the dynamic type of basep double undiscounted = basep->quote::net_price(42); calls the Quote version of net_price regardless of the type of the object to which basep actually points. This call will be resolved at compile time. Note Ordinarily, only code inside member functions (or friends) should need to use the scope operator to circumvent the virtual mechanism. Why might we wish to circumvent the virtual mechanism? The most common reason is when a derived-class virtual function calls the version from the base class. In such cases, the base-class version might do work common to all types in the hierarchy. The versions defined in the derived classes would do whatever additional work is particular to their own type. Warning If a derived virtual function that intended to call its base-class version omits the scope operator, the call will be resolved at run time as a call to the derived version itself, resulting in an infinite recursion. Exercises Section 15.3 Exercise 15.11: Add a virtual debug function to your Quote class hierarchy that displays the data members of the respective classes. Exercise 15.12: Is it ever useful to declare a member function as both override and final? Why or why not?

22 Exercise 15.13: Given the following classes, explain each print function: class base { public: string name() { return basename; } virtual void print(ostream &os) { os << basename; } private: string basename; class derived : public base { public: void print(ostream &os) { print(os); os << " " << i; } private: int i; If there is a problem in this code, how would you fix it? Exercise 15.14: Given the classes from the previous exercise and the following objects, determine which function is called at run time: base bobj; base *bp1 = &bobj; base &br1 = bobj; derived dobj; base *bp2 = &dobj; base &br2 = dobj; (a) bobj.print(); (b) dobj.print(); (c) bp1->name(); (d) bp2->name(); (e) br1.print(); (f) br2.print(); Abstract Base Classes Imagine that we want to extend our bookstore classes to support several discount strategies. In addition to a bulk discount, we might offer a discount for purchases up to a certain quantity and then charge the full price thereafter. Or we might offer a discount for purchases above a certain limit but not for purchases up to that limit. Each of these discount strategies is the same in that it requires a quantity and a discount amount. We might support these differing strategies by defining a new class named Disc_quote to store the quantity and the discount amount. Classes, such as Bulk_item, that represent a specific discount strategy will inherit from Disc_quote. Each of the derived classes will implement its discount strategy by defining its own

23 version of net_price. Before we can define our Disc_Quote class, we have to decide what to do about net_price. Our Disc_quote class doesn t correspond to any particular discount strategy; there is no meaning to ascribe to net_price for this class. We could define Disc_quote without its own version of net_price. In this case, Disc_quote would inherit net_price from Quote. However, this design would make it possible for our users to write nonsensical code. A user could create an object of type Disc_quote by supplying a quantity and a discount rate. Passing that Disc_quote object to a function such as print_total would use the Quote version of net_price. The calculated price would not include the discount that was supplied when the object was created. That state of affairs makes no sense. Pure Virtual Functions Thinking about the question in this detail reveals that our problem is not just that we don t know how to define net_price. In practice, we d like to prevent users from creating Disc_quote objects at all. This class represents the general concept of a discounted book, not a concrete discount strategy. We can enforce this design intent and make it clear that there is no meaning for net_price by defining net_price as a pure virtual function. Unlike ordinary virtuals, a pure virtual function does not have to be defined. We specify that a virtual function is a pure virtual by writing = 0 in place of a function body (i.e., just before the semicolon that ends the declaration). The = 0 may appear only on the declaration of a virtual function in the class body: // class to hold the discount rate and quantity // derived classes will implement pricing strategies using these data class Disc_quote : public Quote { public: Disc_quote() = default; Disc_quote(const std::string& book, double price, std::size_t qty, double disc): Quote(book, price), quantity(qty), discount(disc) { } double net_price(std::size_t) const = 0; protected: std::size_t quantity = 0; // purchase size for the discount to apply double discount = 0.0; // fractional discount to apply Like our earlier Bulk_item class, Disc_quote defines a default constructor and a constructor that takes four parameters. Although we cannot define objects of this type directly, constructors in classes derived from Disc_quote will use the Disc_quote

24 constructors to construct the Disc_quote part of their objects. The constructor that has four parameters passes its first two to the Quote constructor and directly initializes its own members, discount and quantity. The default constructor default initializes those members. It is worth noting that we can provide a definition for a pure virtual. However, the function body must be defined outside the class. That is, we cannot provide a function body inside the class for a function that is = 0. Classes with Pure Virtuals Are Abstract Base Classes A class containing (or inheriting without overridding) a pure virtual function is an abstract base class. An abstract base class defines an interface for subsequent classes to override. We cannot (directly) create objects of a type that is an abstract base class. Because Disc_quote defines net_price as a pure virtual, we cannot define objects of type Disc_quote. We can define objects of classes that inherit from Disc_quote, so long as those classes override net_price: // Disc_quote declares pure virtual functions, which Bulk_quote will override Disc_quote discounted; // error: can't define a Disc_quote object Bulk_quote bulk; // ok: Bulk_quote has no pure virtual functions Classes that inherit from Disc_quote must define net_price or those classes will be abstract as well. Note We may not create objects of a type that is an abstract base class. A Derived Class Constructor Initializes Its Direct Base Class Only Now we can reimplement Bulk_quote to inherit from Disc_quote rather than inheriting directly from Quote: // the discount kicks in when a specified number of copies of the same book are sold // the discount is expressed as a fraction to use to reduce the normal price class Bulk_quote : public Disc_quote { public: Bulk_quote() = default; Bulk_quote(const std::string& book, double price, std::size_t qty, double disc): Disc_quote(book, price, qty, disc) { }

25 // overrides the base version to implement the bulk purchase discount policy double net_price(std::size_t) const override; This version of Bulk_quote has a direct base class, Disc_quote, and an indirect base class, Quote. Each Bulk_quote object has three subobjects: an (empty) Bulk_quote part, a Disc_quote subobject, and a Quote subobject. As we ve seen, each class controls the initialization of objects of its type. Therefore, even though Bulk_quote has no data members of its own, it provides the same four-argument constructor as in our original class. Our new constructor passes its arguments to the Disc_quote constructor. That constructor in turn runs the Quote constructor. The Quote constructor initializes the bookno and price members of bulk. When the Quote constructor ends, the Disc_quote constructor runs and initializes the quantity and discount members. At this point, the Bulk_quote constructor resumes. That constructor has no further initializations or any other work to do. Key Concept: Refactoring Adding Disc_quote to the Quote hierarchy is an example of refactoring. Refactoring involves redesigning a class hierarchy to move operations and/or data from one class to another. Refactoring is common in object-oriented applications. It is noteworthy that even though we changed the inheritance hierarchy, code that uses Bulk_quote or Quote would not need to change. However, when classes are refactored (or changed in any other way) we must recompile any code that uses those classes. Exercises Section 15.4 Exercise 15.15: Define your own versions of Disc_quote and Bulk_quote. Exercise 15.16: Rewrite the class representing a limited discount strategy, which you wrote for the exercises in (p. 601), to inherit from Disc_quote. Exercise 15.17: Try to define an object of type Disc_quote and see what errors you get from the compiler Access Control and Inheritance

26 Just as each class controls the initialization of its own members ( , p. 598), each class also controls whether its members are accessible to a derived class. protected Members As we ve seen, a class uses protected for those members that it is willing to share with its derived classes but wants to protect from general access. The protected specifier can be thought of as a blend of private and public: Like private, protected members are inaccessible to users of the class. Like public, protected members are accessible to members and friends of classes derived from this class. In addition, protected has another important property: A derived class member or friend may access the protected members of the base class only through a derived object. The derived class has no special access to the protected members of base-class objects. To understand this last rule, consider the following example: class Base { protected: int prot_mem; // protected member class Sneaky : public Base { friend void clobber(sneaky&); // can access Sneaky::prot_mem friend void clobber(base&); // can't access Base::prot_mem int j; // j is private by default // ok: clobber can access the private and protected members in Sneaky objects void clobber(sneaky &s) { s.j = s.prot_mem = 0; } // error: clobber can't access the protected members in Base void clobber(base &b) { b.prot_mem = 0; } If derived classes (and friends) could access protected members in a base-class object, then our second version of clobber (that takes a Base&) would be legal. That function is not a friend of Base, yet it would be allowed to change an object of type Base; we could circumvent the protection provided by protected for any class simply by defining a new class along the lines of Sneaky. To prevent such usage, members and friends of a derived class can access the protected members only in base-class objects that are embedded inside a derived type object; they have no special access to ordinary objects of the base type. public, private, and protected Inheritance

27 Access to a member that a class inherits is controlled by a combination of the access specifier for that member in the base class, and the access specifier in the derivation list of the derived class. As an example, consider the following hierarchy: class Base { public: void pub_mem(); // public member protected: int prot_mem; // protected member private: char priv_mem; // private member struct Pub_Derv : public Base { // ok: derived classes can access protected members int f() { return prot_mem; } // error: private members are inaccessible to derived classes char g() { return priv_mem; } struct Priv_Derv : private Base { // private derivation doesn't affect access in the derived class int f1() const { return prot_mem; } The derivation access specifier has no effect on whether members (and friends) of a derived class may access the members of its own direct base class. Access to the members of a base class is controlled by the access specifiers in the base class itself. Both Pub_Derv and Priv_Derv may access the protected member prot_mem. Neither may access the private member priv_mem. The purpose of the derivation access specifier is to control the access that users of the derived class including other classes derived from the derived class have to the members inherited from Base: Pub_Derv d1; // members inherited from Base are public Priv_Derv d2; // members inherited from Base are private d1.pub_mem(); // ok: pub_mem is public in the derived class d2.pub_mem(); // error: pub_mem is private in the derived class Both Pub_Derv and Priv_Derv inherit the pub_mem function. When the inheritance is public, members retain their access specification. Thus, d1 can call pub_mem. In Priv_Derv, the members of Base are private; users of that class may not call pub_mem. The derivation access specifier used by a derived class also controls access from classes that inherit from that derived class:

28 struct Derived_from_Public : public Pub_Derv { // ok: Base::prot_mem remains protected in Pub_Derv int use_base() { return prot_mem; } struct Derived_from_Private : public Priv_Derv { // error: Base::prot_mem is private in Priv_Derv int use_base() { return prot_mem; } Classes derived from Pub_Derv may access prot_mem from Base because that member remains a protected member in Pub_Derv. In contrast, classes derived from Priv_Derv have no such access. To them, all the members that Priv_Derv inherited from Base are private. Had we defined another class, say, Prot_Derv, that used protected inheritance, the public members of Base would be protected members in that class. Users of Prot_Derv would have no access to pub_mem, but the members and friends of Prot_Derv could access that inherited member. Accessibility of Derived-to-Base Conversion Whether the derived-to-base conversion ( , p. 597) is accessible depends on which code is trying to use the conversion and may depend on the access specifier used in the derived class derivation. Assuming D inherits from B: User code may use the derived-to-base conversion only if D inherits publicly from B. User code may not use the conversion if D inherits from B using either protected or private. Member functions and friends of D can use the conversion to B regardless of how D inherits from B. The derived-to-base conversion to a direct base class is always accessible to members and friends of a derived class. Member functions and friends of classes derived from D may use the derived-tobase conversion if D inherits from B using either public or protected. Such code may not use the conversion if D inherits privately from B. Tip For any given point in your code, if a public member of the base class would be accessible, then the derived-to-base conversion is also accessible, and not otherwise. Key Concept: Class Design and protected Members

29 In the absence of inheritance, we can think of a class as having two different kinds of users: ordinary users and implementors. Ordinary users write code that uses objects of the class type; such code can access only the public (interface) members of the class. Implementors write the code contained in the members and friends of the class. The members and friends of the class can access both the public and private (implementation) sections. Under inheritance, there is a third kind of user, namely, derived classes. A base class makes protected those parts of its implementation that it is willing to let its derived classes use. The protected members remain inaccessible to ordinary user code; private members remain inaccessible to derived classes and their friends. Like any other class, a class that is used as a base class makes its interface members public. A class that is used as a base class may divide its implementation into those members that are accessible to derived classes and those that remain accessible only to the base class and its friends. An implementation member should be protected if it provides an operation or data that a derived class will need to use in its own implementation. Otherwise, implementation members should be private. Friendship and Inheritance Just as friendship is not transitive ( 7.3.4, p. 279), friendship is also not inherited. Friends of the base have no special access to members of its derived classes, and friends of a derived class have no special access to the base class: class Base { // added friend declaration; other members as before friend class Pal; // Pal has no access to classes derived from Base class Pal { public: int f(base b) { return b.prot_mem; } // ok: Pal is a friend of Base int f2(sneaky s) { return s.j; } // error: Pal not friend of Sneaky // access to a base class is controlled by the base class, even inside a derived object int f3(sneaky s) { return s.prot_mem; } // ok: Pal is a friend The fact that f3 is legal may seem surprising, but it follows directly from the notion

30 that each class controls access to its own members. Pal is a friend of Base, so Pal can access the members of Base objects. That access includes access to Base objects that are embedded in an object of a type derived from Base. When a class makes another class a friend, it is only that class to which friendship is granted. The base classes of, and classes derived from, the friend have no special access to the befriending class: // D2 has no access to protected or private members in Base class D2 : public Pal { public: int mem(base b) { return b.prot_mem; } // error: friendship doesn't inherit Note Friendship is not inherited; each class controls access to its members. Exempting Individual Members Sometimes we need to change the access level of a name that a derived class inherits. We can do so by providing a using declaration ( 3.1, p. 82): class Base { public: std::size_t size() const { return n; } protected: std::size_t n; class Derived : private Base { // note: private inheritance public: // maintain access levels for members related to the size of the object using Base::size; protected: using Base::n; Because Derived uses private inheritance, the inherited members, size and n, are (by default) private members of Derived. The using declarations adjust the accessibility of these members. Users of Derived can access the size member, and classes subsequently derived from Derived can access n. A using declaration inside a class can name any accessible (e.g., not private)

31 member of a direct or indirect base class. Access to a name specified in a using declaration depends on the access specifier preceding the using declaration. That is, if a using declaration appears in a private part of the class, that name is accessible to members and friends only. If the declaration is in a public section, the name is available to all users of the class. If the declaration is in a protected section, the name is accessible to the members, friends, and derived classes. Note A derived class may provide a using declaration only for names it is permitted to access. Default Inheritance Protection Levels In 7.2 (p. 268) we saw that classes defined with the struct and class keywords have different default access specifiers. Similarly, the default derivation specifier depends on which keyword is used to define a derived class. By default, a derived class defined with the class keyword has private inheritance; a derived class defined with struct has public inheritance: class Base { /*... */ struct D1 : Base { /*... */ // public inheritance by default class D2 : Base { /*... */ // private inheritance by default It is a common misconception to think that there are deeper differences between classes defined using the struct keyword and those defined using class. The only differences are the default access specifier for members and the default derivation access specifier. There are no other distinctions. Best Practices A privately derived class should specify private explicitly rather than rely on the default. Being explicit makes it clear that private inheritance is intended and not an oversight. Exercises Section 15.5 Exercise 15.18: Given the classes from page 612 and page 613, and assuming each object has the type specified in the comments, determine which of these assignments are legal. Explain why those that are illegal aren t

32 allowed: Base *p = &d1; // d1 has type Pub_Derv p = &d2; // d2 has type Priv_Derv p = &d3; // d3 has type Prot_Derv p = &dd1; // dd1 has type Derived_from_Public p = &dd2; // dd2 has type Derived_from_Private p = &dd3; // dd3 has type Derived_from_Protected Exercise 15.19: Assume that each of the classes from page 612 and page 613 has a member function of the form: void memfcn(base &b) { b = *this; } For each class, determine whether this function would be legal. Exercise 15.20: Write code to test your answers to the previous two exercises. Exercise 15.21: Choose one of the following general abstractions containing a family of types (or choose one of your own). Organize the types into an inheritance hierarchy: (a) Graphical file formats (such as gif, tiff, jpeg, bmp) (b) Geometric primitives (such as box, circle, sphere, cone) (c) C++ language types (such as class, function, member function) Exercise 15.22: For the class you chose in the previous exercise, identify some of the likely virtual functions as well as public and protected members Class Scope under Inheritance Each class defines its own scope ( 7.4, p. 282) within which its members are defined. Under inheritance, the scope of a derived class is nested ( 2.2.4, p. 48) inside the scope of its base classes. If a name is unresolved within the scope of the derived class, the enclosing base-class scopes are searched for a definition of that name. The fact that the scope of a derived class nests inside the scope of its base classes can be surprising. After all, the base and derived classes are defined in separate parts of our program s text. However, it is this hierarchical nesting of class scopes that allows the members of a derived class to use members of its base class as if those members were part of the derived class. For example, when we write Bulk_quote bulk;

33 cout << bulk.isbn(); the use of the name isbn is resolved as follows: Because we called isbn on an object of type Bulk_quote, the search starts in the Bulk_quote class. The name isbn is not found in that class. Because Bulk_quote is derived from Disc_quote, the Disc_quote class is searched next. The name is still not found. Because Disc_quote is derived from Quote, the Quote class is searched next. The name isbn is found in that class; the use of isbn is resolved to the isbn in Quote. Name Lookup Happens at Compile Time The static type ( , p. 601) of an object, reference, or pointer determines which members of that object are visible. Even when the static and dynamic types might differ (as can happen when a reference or pointer to a base class is used), the static type determines what members can be used. As an example, we might add a member to the Disc_quote class that returns a pair ( , p. 426) holding the minimum (or maximum) quantity and the discounted price: class Disc_quote : public Quote { public: std::pair<size_t, double> discount_policy() const { return {quantity, discount } // other members as before We can use discount_policy only through an object, pointer, or reference of type Disc_quote or of a class derived from Disc_quote: Bulk_quote bulk; Bulk_quote *bulkp = &bulk; // static and dynamic types are the same Quote *itemp = &bulk; // static and dynamic types differ bulkp->discount_policy(); // ok: bulkp has type Bulk_quote* itemp->discount_policy(); // error: itemp has type Quote* Even though bulk has a member named discount_policy, that member is not visible through itemp. The type of itemp is a pointer to Quote, which means that the search for discount_policy starts in class Quote. The Quote class has no member named discount_policy, so we cannot call that member on an object, reference, or pointer of type Quote. Name Collisions and Inheritance

34 Like any other scope, a derived class can reuse a name defined in one of its direct or indirect base classes. As usual, names defined in an inner scope (e.g., a derived class) hide uses of that name in the outer scope (e.g., a base class) ( 2.2.4, p. 48): struct Base { Base(): mem(0) { } protected: int mem; struct Derived : Base { Derived(int i): mem(i) { } // initializes Derived::mem to i // Base::mem is default initialized int get_mem() { return mem; } // returns Derived::mem protected: int mem; // hides mem in the base The reference to mem inside get_mem is resolved to the name inside Derived. Were we to write Derived d(42); cout << d.get_mem() << endl; // prints 42 then the output would be 42. Note A derived-class member with the same name as a member of the base class hides direct use of the base-class member. Using the Scope Operator to Use Hidden Members We can use a hidden base-class member by using the scope operator: struct Derived : Base { int get_base_mem() { return Base::mem; } //... The scope operator overrides the normal lookup and directs the compiler to look for mem starting in the scope of class Base. If we ran the code above with this version of Derived, the result of d.get_mem() would be 0.

35 Best Practices Aside from overriding inherited virtual functions, a derived class usually should not reuse names defined in its base class. Key Concept: Name Lookup and Inheritance Understanding how function calls are resolved is crucial to understanding inheritance in C++. Given the call p->mem() (or obj.mem()), the following four steps happen: First determine the static type of p (or obj). Because we re calling a member, that type must be a class type. Look for mem in the class that corresponds to the static type of p (or obj). If mem is not found, look in the direct base class and continue up the chain of classes until mem is found or the last class is searched. If mem is not found in the class or its enclosing base classes, then the call will not compile. Once mem is found, do normal type checking ( 6.1, p. 203) to see if this call is legal given the definition that was found. Assuming the call is legal, the compiler generates code, which varies depending on whether the call is virtual or not: If mem is virtual and the call is made through a reference or pointer, then the compiler generates code to determine at run time which version to run based on the dynamic type of the object. Otherwise, if the function is nonvirtual, or if the call is on an object (not a reference or pointer), the compiler generates a normal function call. As Usual, Name Lookup Happens before Type Checking As we ve seen, functions declared in an inner scope do not overload functions declared in an outer scope ( 6.4.1, p. 234). As a result, functions defined in a derived class do not overload members defined in its base class(es). As in any other scope, if a member in a derived class (i.e., in an inner scope) has the same name as a baseclass member (i.e., a name defined in an outer scope), then the derived member hides the base-class member within the scope of the derived class. The base member is hidden even if the functions have different parameter lists:

36 struct Base { int memfcn(); struct Derived : Base { int memfcn(int); // hides memfcn in the base Derived d; Base b; b.memfcn(); // calls Base::memfcn d.memfcn(10); // calls Derived::memfcn d.memfcn(); // error: memfcn with no arguments is hidden d.base::memfcn(); // ok: calls Base::memfcn The declaration of memfcn in Derived hides the declaration of memfcn in Base. Not surprisingly, the first call through b, which is a Base object, calls the version in the base class. Similarly, the second call (through d) calls the one from Derived. What can be surprising is that the third call, d.memfcn(), is illegal. To resolve this call, the compiler looks for the name memfcn in Derived. That class defines a member named memfcn and the search stops. Once the name is found, the compiler looks no further. The version of memfcn in Derived expects an int argument. This call provides no such argument; it is in error. Virtual Functions and Scope We can now understand why virtual functions must have the same parameter list in the base and derived classes ( 15.3, p. 605). If the base and derived members took arguments that differed from one another, there would be no way to call the derived version through a reference or pointer to the base class. For example: class Base { public: virtual int fcn(); class D1 : public Base { public: // hides fcn in the base; this fcn is not virtual // D1 inherits the definition of Base::fcn() int fcn(int); // parameter list differs from fcn in Base virtual void f2(); // new virtual function that does not exist in Base class D2 : public D1 { public: int fcn(int); // nonvirtual function hides D1::fcn(int) int fcn(); // overrides virtual fcn from Base void f2(); // overrides virtual f2 from D1

37 The fcn function in D1 does not override the virtual fcn from Base because they have different parameter lists. Instead, it hides fcn from the base. Effectively, D1 has two functions named fcn: D1 inherits a virtual named fcn from Base and defines its own, nonvirtual member named fcn that takes an int parameter. Calling a Hidden Virtual through the Base Class Given the classes above, let s look at several different ways to call these functions: Base bobj; D1 d1obj; D2 d2obj; Base *bp1 = &bobj, *bp2 = &d1obj, *bp3 = &d2obj; bp1->fcn(); // virtual call, will call Base::fcn at run time bp2->fcn(); // virtual call, will call Base::fcn at run time bp3->fcn(); // virtual call, will call D2::fcn at run time D1 *d1p = &d1obj; D2 *d2p = &d2obj; bp2->f2(); // error: Base has no member named f2 d1p->f2(); // virtual call, will call D1::f2() at run time d2p->f2(); // virtual call, will call D2::f2() at run time The first three calls are all made through pointers to the base class. Because fcn is virtual, the compiler generates code to decide at run time which version to call. That decision will be based on the actual type of the object to which the pointer is bound. In the case of bp2, the underlying object is a D1. That class did not override the fcn function that takes no arguments. Thus, the call through bp2 is resolved (at run time) to the version defined in Base. The next three calls are made through pointers with differing types. Each pointer points to one of the types in this hierarchy. The first call is illegal because there is no f2() in class Base. The fact that the pointer happens to point to a derived object is irrelevant. For completeness, let s look at calls to the nonvirtual function fcn(int): Base *p1 = &d2obj; D1 *p2 = &d2obj; D2 *p3 = &d2obj; p1->fcn(42); // error: Base has no version of fcn that takes an int p2->fcn(42); // statically bound, calls D1::fcn(int) p3->fcn(42); // statically bound, calls D2::fcn(int) In each call the pointer happens to point to an object of type D2. However, the dynamic type doesn t matter when we call a nonvirtual function. The version that is called depends only on the static type of the pointer. Overriding Overloaded Functions

38 As with any other function, a member function (virtual or otherwise) can be overloaded. A derived class can override zero or more instances of the overloaded functions it inherits. If a derived class wants to make all the overloaded versions available through its type, then it must override all of them or none of them. Sometimes a class needs to override some, but not all, of the functions in an overloaded set. It would be tedious in such cases to have to override every base-class version in order to override the ones that the class needs to specialize. Instead of overriding every base-class version that it inherits, a derived class can provide a using declaration ( 15.5, p. 615) for the overloaded member. A using declaration specifies only a name; it may not specify a parameter list. Thus, a using declaration for a base-class member function adds all the overloaded instances of that function to the scope of the derived class. Having brought all the names into its scope, the derived class needs to define only those functions that truly depend on its type. It can use the inherited definitions for the others. The normal rules for a using declaration inside a class apply to names of overloaded functions ( 15.5, p. 615); every overloaded instance of the function in the base class must be accessible to the derived class. The access to the overloaded versions that are not otherwise redefined by the derived class will be the access in effect at the point of the using declaration. Exercises Section 15.6 Exercise 15.23: Assuming class D1 on page 620 had intended to override its inherited fcn function, how would you fix that class? Assuming you fixed the class so that fcn matched the definition in Base, how would the calls in that section be resolved? Constructors and Copy Control Like any other class, a class in an inheritance hierarchy controls what happens when objects of its type are created, copied, moved, assigned, or destroyed. As for any other class, if a class (base or derived) does not itself define one of the copy-control operations, the compiler will synthesize that operation. Also, as usual, the synthesized version of any of these members might be a deleted function Virtual Destructors The primary direct impact that inheritance has on copy control for a base class is that a base class generally should define a virtual destructor ( , p. 594). The

39 destructor needs to be virtual to allow objects in the inheritance hierarchy to be dynamically allocated. Recall that the destructor is run when we delete a pointer to a dynamically allocated object ( , p. 502). If that pointer points to a type in an inheritance hierarchy, it is possible that the static type of the pointer might differ from the dynamic type of the object being destroyed ( , p. 597). For example, if we delete a pointer of type Quote*, that pointer might point at a Bulk_quote object. If the pointer points at a Bulk_quote, the compiler has to know that it should run the Bulk_quote destructor. As with any other function, we arrange to run the proper destructor by defining the destructor as virtual in the base class: class Quote { public: // virtual destructor needed if a base pointer pointing to a derived object is deleted virtual ~Quote() = default; // dynamic binding for the destructor Like any other virtual, the virtual nature of the destructor is inherited. Thus, classes derived from Quote have virtual destructors, whether they use the synthesized destructor or define their own version. So long as the base class destructor is virtual, when we delete a pointer to base, the correct destructor will be run: Quote *itemp = new Quote; delete itemp; itemp = new Bulk_quote; delete itemp; // same static and dynamic type // destructor for Quote called // static and dynamic types differ // destructor for Bulk_quote called Warning Executing delete on a pointer to base that points to a derived object has undefined behavior if the base s destructor is not virtual. Destructors for base classes are an important exception to the rule of thumb that if a class needs a destructor, it also needs copy and assignment ( , p. 504). A base class almost always needs a destructor, so that it can make the destructor virtual. If a base class has an empty destructor in order to make it virtual, then the fact that the class has a destructor does not indicate that the assignment operator or copy constructor is also needed. Virtual Destructors Turn Off Synthesized Move

40 The fact that a base class needs a virtual destructor has an important indirect impact on the definition of base and derived classes: If a class defines a destructor even if it uses = default to use the synthesized version the compiler will not synthesize a move operation for that class ( , p. 537). Exercises Section Exercise 15.24: What kinds of classes need a virtual destructor? What operations must a virtual destructor perform? Synthesized Copy Control and Inheritance The synthesized copy-control members in a base or a derived class execute like any other synthesized constructor, assignment operator, or destructor: They memberwise initialize, assign, or destroy the members of the class itself. In addition, these synthesized members initialize, assign, or destroy the direct base part of an object by using the corresponding operation from the base class. For example, The synthesized Bulk_quote default constructor runs the Disc_Quote default constructor, which in turn runs the Quote default constructor. The Quote default constructor default initializes the bookno member to the empty string and uses the in-class initializer to initialize price to zero. When the Quote constructor finishes, the Disc_Quote constructor continues, which uses the in-class initializers to initialize qty and discount. When the Disc_quote constructor finishes, the Bulk_quote constructor continues but has no other work to do. Similarly, the synthesized Bulk_quote copy constructor uses the (synthesized) Disc_quote copy constructor, which uses the (synthesized) Quote copy constructor. The Quote copy constructor copies the bookno and price members; and the Disc_Quote copy constructor copies the qty and discount members. It is worth noting that it doesn t matter whether the base-class member is itself synthesized (as is the case in our Quote hierarchy) or has a an user-provided definition. All that matters is that the corresponding member is accessible ( 15.5, p. 611) and that it is not a deleted function. Each of our Quote classes use the synthesized destructor. The derived classes do so implicitly, whereas the Quote class does so explicitly by defining its (virtual) destructor as = default. The synthesized destructor is (as usual) empty and its implicit destruction part destroys the members of the class ( , p. 501). In addition to destroying its own members, the destruction phase of a destructor in a derived class also destroys its direct base. That destructor in turn invokes the

41 destructor for its own direct base, if any. And, so on up to the root of the hierarchy. As we ve seen, Quote does not have synthesized move operations because it defines a destructor. The (synthesized) copy operations will be used whenever we move a Quote object ( , p. 540). As we re about to see, the fact that Quote does not have move operations means that its derived classes don t either. Base Classes and Deleted Copy Control in the Derived The synthesized default constructor, or any of the copy-control members of either a base or a derived class, may be defined as deleted for the same reasons as in any other class ( , p. 508, and , p. 537). In addition, the way in which a base class is defined can cause a derived-class member to be defined as deleted: If the default constructor, copy constructor, copy-assignment operator, or destructor in the base class is deleted or inaccessible ( 15.5, p. 612), then the corresponding member in the derived class is defined as deleted, because the compiler can t use the base-class member to construct, assign, or destroy the base-class part of the object. If the base class has an inaccessible or deleted destructor, then the synthesized default and copy constructors in the derived classes are defined as deleted, because there is no way to destroy the base part of the derived object. As usual, the compiler will not synthesize a deleted move operation. If we use = default to request a move operation, it will be a deleted function in the derived if the corresponding operation in the base is deleted or inaccessible, because the base class part cannot be moved. The move constructor will also be deleted if the base class destructor is deleted or inaccessible. As an example, this base class, B, class B { public: B(); B(const B&) = delete; // other members, not including a move constructor class D : public B { // no constructors D d; // ok: D's synthesized default constructor uses B's default constructor D d2(d); // error: D's synthesized copy constructor is deleted D d3(std::move(d)); // error: implicitly uses D's deleted copy constructor has an accessible default constructor and an explicitly deleted copy constructor. Because the copy constructor is defined, the compiler will not synthesize a move

42 constructor for class B ( , p. 537). As a result, we can neither move nor copy objects of type B. If a class derived from B wanted to allow its objects to be copied or moved, that derived class would have to define its own versions of these constructors. Of course, that class would have to decide how to copy or move the members in it base-class part. In practice, if a base class does not have a default, copy, or move constructor, then its derived classes usually don t either. Move Operations and Inheritance As we ve seen, most base classes define a virtual destructor. As a result, by default, base classes generally do not get synthesized move operations. Moreover, by default, classes derived from a base class that doesn t have move operations don t get synthesized move operations either. Because lack of a move operation in a base class suppresses synthesized move for its derived classes, base classes ordinarily should define the move operations if it is sensible to do so. Our Quote class can use the synthesized versions. However, Quote must define these members explicitly. Once it defines its move operations, it must also explicitly define the copy versions as well ( , p. 539): class Quote { public: Quote() = default; // memberwise default initialize Quote(const Quote&) = default; // memberwise copy Quote(Quote&&) = default; // memberwise copy Quote& operator=(const Quote&) = default; // copy assign Quote& operator=(quote&&) = default; // move assign virtual ~Quote() = default; // other members as before Now, Quote objects will be memberwise copied, moved, assigned, and destroyed. Moreover, classes derived from Quote will automatically obtain synthesized move operations as well, unless they have members that otherwise preclude move. Exercises Section Exercise 15.25: Why did we define a default constructor for Disc_quote? What effect, if any, would removing that constructor have on the behavior of Bulk_quote? Derived-Class Copy-Control Members

43 As we saw in (p. 598), the initialization phase of a derived-class constructor initializes the base-class part(s) of a derived object as well as initializing its own members. As a result, the copy and move constructors for a derived class must copy/move the members of its base part as well as the members in the derived. Similarly, a derived-class assignment operator must assign the members in the base part of the derived object. Unlike the constructors and assignment operators, the destructor is responsible only for destroying the resources allocated by the derived class. Recall that the members of an object are implicitly destroyed ( , p. 502). Similarly, the base-class part of a derived object is destroyed automatically. Warning When a derived class defines a copy or move operation, that operation is responsible for copying or moving the entire object, including base-class members. Defining a Derived Copy or Move Constructor When we define a copy or move constructor ( , p. 496, and , p. 534) for a derived class, we ordinarily use the corresponding base-class constructor to initialize the base part of the object: class Base { /*... */ } ; class D: public Base { public: // by default, the base class default constructor initializes the base part of an object // to use the copy or move constructor, we must explicitly call that // constructor in the constructor initializer list D(const D& d): Base(d) // copy the base members /* initializers for members of D */ { /*... */ } D(D&& d): Base(std::move(d)) // move the base members /* initializers for members of D */ { /*... */ } The initializer Base(d) passes a D object to a base-class constructor. Although in principle, Base could have a constructor that has a parameter of type D, in practice, that is very unlikely. Instead, Base(d) will (ordinarily) match the Base copy constructor. The D object, d, will be bound to the Base& parameter in that

44 constructor. The Base copy constructor will copy the base part of d into the object that is being created. Had the initializer for the base class been omitted, // probably incorrect definition of the D copy constructor // base-class part is default initialized, not copied D(const D& d) /* member initializers, but no base-class initializer */ { /*... */ } the Base default constructor would be used to initialize the base part of a D object. Assuming D s constructor copies the derived members from d, this newly constructed object would be oddly configured: Its Base members would hold default values, while its D members would be copies of the data from another object. Warning By default, the base-class default constructor initializes the base-class part of a derived object. If we want copy (or move) the base-class part, we must explicitly use the copy (or move) constructor for the base class in the derived s constructor initializer list. Derived-Class Assignment Operator Like the copy and move constructors, a derived-class assignment operator ( , p. 500, and , p. 536), must assign its base part explicitly: // Base::operator=(const Base&) is not invoked automatically D &D::operator=(const D &rhs) { Base::operator=(rhs); // assigns the base part // assign the members in the derived class, as usual, // handling self-assignment and freeing existing resources as appropriate return *this; } This operator starts by explicitly calling the base-class assignment operator to assign the members of the base part of the derived object. The base-class operator will (presumably) correctly handle self-assignment and, if appropriate, will free the old value in the base part of the left-hand operand and assign the new values from rhs. Once that operator finishes, we continue doing whatever is needed to assign the members in the derived class. It is worth noting that a derived constructor or assignment operator can use its corresponding base class operation regardless of whether the base defined its own

45 version of that operator or uses the synthesized version. For example, the call to Base::operator= executes the copy-assignment operator in class Base. It is immaterial whether that operator is defined explicitly by the Base class or is synthesized by the compiler. Derived-Class Destructor Recall that the data members of an object are implicitly destroyed after the destructor body completes ( , p. 502). Similarly, the base-class parts of an object are also implicitly destroyed. As a result, unlike the constructors and assignment operators, a derived destructor is responsible only for destroying the resources allocated by the derived class: class D: public Base { public: // Base::~Base invoked automatically ~D() { /* do what it takes to clean up derived members */ } Objects are destroyed in the opposite order from which they are constructed: The derived destructor is run first, and then the base-class destructors are invoked, back up through the inheritance hierarchy. Calls to Virtuals in Constructors and Destructors As we ve seen, the base-class part of a derived object is constructed first. While the base-class constructor is executing, the derived part of the object is uninitialized. Similarly, derived objects are destroyed in reverse order, so that when a base class destructor runs, the derived part has already been destroyed. As a result, while these base-class members are executing, the object is incomplete. To accommodate this incompleteness, the compiler treats the object as if its type changes during construction or destruction. That is, while an object is being constructed it is treated as if it has the same class as the constructor; calls to virtual functions will be bound as if the object has the same type as the constructor itself. Similarly, for destructors. This binding applies to virtuals called directly or that are called indirectly from a function that the constructor (or destructor) calls. To understand this behavior, consider what would happen if the derived-class version of a virtual was called from a base-class constructor. This virtual probably accesses members of the derived object. After all, if the virtual didn t need to use members of the derived object, the derived class probably could use the version in its base class. However, those members are uninitialized while a base constructor is running. If such access were allowed, the program would probably crash.

46 Note If a constructor or destructor calls a virtual, the version that is run is the one corresponding to the type of the constructor or destructor itself. Exercises Section Exercise 15.26: Define the Quote and Bulk_quote copy-control members to do the same job as the synthesized versions. Give them and the other constructors print statements that identify which function is running. Write programs using these classes and predict what objects will be created and destroyed. Compare your predictions with the output and continue experimenting until your predictions are reliably correct Inherited Constructors Under the new standard, a derived class can reuse the constructors defined by its direct base class. Although, as we ll see, such constructors are not inherited in the normal sense of that term, it is nonetheless common to refer to such constructors as inherited. For the same reasons that a class may initialize only its direct base class, a class may inherit constructors only from its direct base. A class cannot inherit the default, copy, and move constructors. If the derived class does not directly define these constructors, the compiler synthesizes them as usual. A derived class inherits its base-class constructors by providing a using declaration that names its (direct) base class. As an example, we can redefine our Bulk_quote class ( 15.4, p. 610) to inherit its constructors from Disc_quote: class Bulk_quote : public Disc_quote { public: using Disc_quote::Disc_quote; // inherit Disc_quote's constructors double net_price(std::size_t) const; Ordinarily, a using declaration only makes a name visible in the current scope. When applied to a constructor, a using declaration causes the compiler to generate code. The compiler generates a derived constructor corresponding to each constructor in the base. That is, for each constructor in the base class, the compiler generates a constructor in the derived class that has the same parameter list. These compiler-generated constructors have the form

47 derived(parms) : base(args) { } where derived is the name of the derived class, base is the name of the base class, parms is the parameter list of the constructor, and args pass the parameters from the derived constructor to the base constructor. In our Bulk_quote class, the inherited constructor would be equivalent to Bulk_quote(const std::string& book, double price, std::size_t qty, double disc): Disc_quote(book, price, qty, disc) { } If the derived class has any data members of its own, those members are default initialized ( 7.1.4, p. 266). Characteristics of an Inherited Constructor Unlike using declarations for ordinary members, a constructor using declaration does not change the access level of the inherited constructor(s). For example, regardless of where the using declaration appears, a private constructor in the base is a private constructor in the derived; similarly for protected and public constructors. Moreover, a using declaration can t specify explicit or constexpr. If a constructor in the base is explicit ( 7.5.4, p. 296) or constexpr ( 7.5.6, p. 299), the inherited constructor has the same property. If a base-class constructor has default arguments ( 6.5.1, p. 236), those arguments are not inherited. Instead, the derived class gets multiple inherited constructors in which each parameter with a default argument is successively omitted. For example, if the base has a constructor with two parameters, the second of which has a default, the derived class will obtain two constructors: one with both parameters (and no default argument) and a second constructor with a single parameter corresponding to the left-most, non-defaulted parameter in the base class. If a base class has several constructors, then with two exceptions, the derived class inherits each of the constructors from its base class. The first exception is that a derived class can inherit some constructors and define its own versions of other constructors. If the derived class defines a constructor with the same parameters as a constructor in the base, then that constructor is not inherited. The one defined in the derived class is used in place of the inherited constructor. The second exception is that the default, copy, and move constructors are not inherited. These constructors are synthesized using the normal rules. An inherited constructor is not treated as a user-defined constructor. Therefore, a class that contains only inherited constructors will have a synthesized default constructor. Exercises Section

48 Exercise 15.27: Redefine your Bulk_quote class to inherit its constructors Containers and Inheritance When we use a container to store objects from an inheritance hierarchy, we generally must store those objects indirectly. We cannot put objects of types related by inheritance directly into a container, because there is no way to define a container that holds elements of differing types. As an example, assume we want to define a vector to hold several books that a customer wants to buy. It should be easy to see that we can t use a vector that holds Bulk_quote objects. We can t convert Quote objects to Bulk_quote ( , p. 602), so we wouldn t be able to put Quote objects into that vector. It may be somewhat less obvious that we also can t use a vector that holds objects of type Quote. In this case, we can put Bulk_quote objects into the container. However, those objects would no longer be Bulk_quote objects: vector<quote> basket; basket.push_back(quote(" ", 50)); // ok, but copies only the Quote part of the object into basket basket.push_back(bulk_quote(" ", 50, 10,.25)); // calls version defined by Quote, prints 750, i.e., 15 * $50 cout << basket.back().net_price(15) << endl; The elements in basket are Quote objects. When we add a Bulk_quote object to the vector its derived part is ignored ( , p. 603). Warning Because derived objects are sliced down when assigned to a base-type object, containers and types related by inheritance do not mix well. Put (Smart) Pointers, Not Objects, in Containers When we need a container that holds objects related by inheritance, we typically define the container to hold pointers (preferably smart pointers ( 12.1, p. 450)) to the base class. As usual, the dynamic type of the object to which those pointers point might be the base-class type or a type derived from that base:

49 vector<shared_ptr<quote>> basket; basket.push_back(make_shared<quote>(" ", 50)); basket.push_back( make_shared<bulk_quote>(" ", 50, 10,.25)); // calls the version defined by Quote; prints 562.5, i.e., 15 * $50 less the discount cout << basket.back()->net_price(15) << endl; Because basket holds shared_ptrs, we must dereference the value returned by basket.back() to get the object on which to run net_price. We do so by using - > in the call to net_price. As usual, the version of net_price that is called depends on the dynamic type of the object to which that pointer points. It is worth noting that we defined basket as shared_ptr<quote>, yet in the second push_back we passed a shared_ptr to a Bulk_quote object. Just as we can convert an ordinary pointer to a derived type to a pointer to an base-class type ( , p. 597), we can also convert a smart pointer to a derived type to a smart pointer to an base-class type. Thus, make_shared<bulk_quote> returns a shared_ptr<bulk_quote> object, which is converted to shared_ptr<quote> when we call push_back. As a result, despite appearances, all of the elements of basket have the same type. Exercises Section 15.8 Exercise 15.28: Define a vector to hold Quote objects but put Bulk_quote objects into that vector. Compute the total net_price of all the elements in the vector. Exercise 15.29: Repeat your program, but this time store shared_ptrs to objects of type Quote. Explain any discrepancy in the sum generated by the this version and the previous program. If there is no discrepancy, explain why there isn t one Writing a Basket Class One of the ironies of object-oriented programming in C++ is that we cannot use objects directly to support it. Instead, we must use pointers and references. Because pointers impose complexity on our programs, we often define auxiliary classes to help manage that complexity. We ll start by defining a class to represent a basket: class Basket { public: // Basket uses synthesized default constructor and copy-control members

50 void add_item(const std::shared_ptr<quote> &sale) { items.insert(sale); } // prints the total price for each book and the overall total for all items in the basket double total_receipt(std::ostream&) const; private: // function to compare shared_ptrs needed by the multiset member static bool compare(const std::shared_ptr<quote> &lhs, const std::shared_ptr<quote> &rhs) { return lhs->isbn() < rhs->isbn(); } // multiset to hold multiple quotes, ordered by the compare member std::multiset<std::shared_ptr<quote>, decltype(compare)*> items{compare Our class uses a multiset ( , p. 423) to hold the transactions, so that we can store multiple transactions for the same book, and so that all the transactions for a given book will be kept together ( , p. 424). The elements in our multiset are shared_ptrs and there is no less-than operator for shared_ptr. As a result, we must provide our own comparison operation to order the elements ( , p. 425). Here, we define a private static member, named compare, that compares the isbns of the objects to which the shared_ptrs point. We initialize our multiset to use this comparison function through an in-class initializer ( 7.3.1, p. 274): // multiset to hold multiple quotes, ordered by the compare member std::multiset<std::shared_ptr<quote>, decltype(compare)*> items{compare This declaration can be hard to read, but reading from left to right, we see that we are defining a multiset of shared_ptrs to Quote objects. The multiset will use a function with the same type as our compare member to order the elements. The multiset member is named items, and we re initializing items to use our compare function. Defining the Members of Basket The Basket class defines only two operations. We defined the add_item member inside the class. That member takes a shared_ptr to a dynamically allocated Quote and puts that shared_ptr into the multiset. The second member, total_receipt, prints an itemized bill for the contents of the basket and returns the price for all the items in the basket: double Basket::total_receipt(ostream &os) const {

51 double sum = 0.0; // holds the running total // iter refers to the first element in a batch of elements with the same ISBN // upper_bound returns an iterator to the element just past the end of that batch for (auto iter = items.cbegin(); iter!= items.cend(); iter = items.upper_bound(*iter)) { // we know there's at least one element with this key in the Basket // print the line item for this book sum += print_total(os, **iter, items.count(*iter)); } os << "Total Sale: " << sum << endl; // print the final overall total return sum; } Our for loop starts by defining and initializing iter to refer to the first element in the multiset. The condition checks whether iter is equal to items.cend(). If so, we ve processed all the purchases and we drop out of the for. Otherwise, we process the next book. The interesting bit is the increment expression in the for. Rather than the usual loop that reads each element, we advance iter to refer to the next key. We skip over all the elements that match the current key by calling upper_bound ( , p. 438). The call to upper_bound returns the iterator that refers to the element just past the last one with the same key as in iter. The iterator we get back denotes either the end of the set or the next book. Inside the for loop, we call print_total ( 15.1, p. 593) to print the details for each book in the basket: sum += print_total(os, **iter, items.count(*iter)); The arguments to print_total are an ostream on which to write, a Quote object to process, and a count. When we dereference iter, we get a shared_ptr that points to the object we want to print. To get that object, we must dereference that shared_ptr. Thus, **iter is a Quote object (or an object of a type derived from Quote). We use the multiset count member ( , p. 436) to determine how many elements in the multiset have the same key (i.e., the same ISBN). As we ve seen, print_total makes a virtual call to net_price, so the resulting price depends on the dynamic type of **iter. The print_total function prints the total for the given book and returns the total price that it calculated. We add that result into sum, which we print after we complete the for loop. Hiding the Pointers Users of Basket still have to deal with dynamic memory, because add_item takes a

52 shared_ptr. As a result, users have to write code such as Basket bsk; bsk.add_item(make_shared<quote>("123", 45)); bsk.add_item(make_shared<bulk_quote>("345", 45, 3,.15)); Our next step will be to redefine add_item so that it takes a Quote object instead of a shared_ptr. This new version of add_item will handle the memory allocation so that our users no longer need to do so. We ll define two versions, one that will copy its given object and the other that will move from it ( , p. 544): void add_item(const Quote& sale); // copy the given object void add_item(quote&& sale); // move the given object The only problem is that add_item doesn t know what type to allocate. When it does its memory allocation, add_item will copy (or move) its sale parameter. Somewhere there will be a new expression such as: new Quote(sale) Unfortunately, this expression won t do the right thing: new allocates an object of the type we request. This expression allocates an object of type Quote and copies the Quote portion of sale. However, sale might refer to a Bulk_quote object, in which case, that object will be sliced down. Simulating Virtual Copy We ll solve this problem by giving our Quote classes a virtual member that allocates a copy of itself. class Quote { public: // virtual function to return a dynamically allocated copy of itself // these members use reference qualifiers; see (p. 546) virtual Quote* clone() const & {return new Quote(*this);} virtual Quote* clone() && {return new Quote(std::move(*this));} // other members as before class Bulk_quote : public Quote { Bulk_quote* clone() const & {return new Bulk_quote(*this);}

53 Bulk_quote* clone() && Bulk_quote(std::move(*this));} // other members as before {return Because we have a copy and a move version of add_item, we defined lvalue and rvalue versions of clone ( , p. 546). Each clone function allocates a new object of its own type. The const lvalue reference member copies itself into that newly allocated object; the rvalue reference member moves its own data. Using clone, it is easy to write our new versions of add_item: class Basket { public: void add_item(const Quote& sale) // copy the given object { items.insert(std::shared_ptr<quote>(sale.clone())); } void add_item(quote&& sale) // move the given object { items.insert( std::shared_ptr<quote>(std::move(sale).clone())); } // other members as before Like add_item itself, clone is overloaded based on whether it is called on an lvalue or an rvalue. Thus, the first version of add_item calls the const lvalue version of clone, and the second version calls the rvalue reference version. Note that in the rvalue version, although the type of sale is an rvalue reference type, sale (like any other variable) is an lvalue ( , p. 533). Therefore, we call move to bind an rvalue reference to sale. Our clone function is also virtual. Whether the Quote or Bulk_quote function is run, depends (as usual) on the dynamic type of sale. Regardless of whether we copy or move the data, clone returns a pointer to a newly allocated object, of its own type. We bind a shared_ptr to that object and call insert to add this newly allocated object to items. Note that because shared_ptr supports the derived-tobase conversion ( , p. 597), we can bind a shared_ptr<quote to a Bulk_quote*. new Exercises Section Exercise 15.30: Write your own version of the Basket class and use it to compute prices for the same transactions as you used in the previous exercises Text Queries Revisited

54 As a final example of inheritance, we ll extend our text-query application from 12.3 (p. 484). The classes we wrote in that section let us look for occurrences of a given word in a file. We d like to extend the system to support more complicated queries. In our examples, we ll run queries against the following simple story: Alice Emma has long flowing red hair. Her Daddy says when the wind blows through her hair, it looks almost alive, like a fiery bird in flight. A beautiful fiery bird, he tells her, magical but untamed. "Daddy, shush, there is no such thing," she tells him, at the same time wanting him to tell her more. Shyly, she asks, "I mean, Daddy, is there?" Our system should support the following queries: Word queries find all the lines that match a given string: Executing Query for: Daddy Daddy occurs 3 times (line 2) Her Daddy says when the wind blows (line 7) "Daddy, shush, there is no such thing," (line 10) Shyly, she asks, "I mean, Daddy, is there?" Not queries, using the ~ operator, yield lines that don t match the query: Executing Query for: ~(Alice) ~(Alice) occurs 9 times (line 2) Her Daddy says when the wind blows (line 3) through her hair, it looks almost alive, (line 4) like a fiery bird in flight.... Or queries, using the operator, return lines matching either of two queries: Executing Query for: (hair Alice) (hair Alice) occurs 2 times (line 1) Alice Emma has long flowing red hair. (line 3) through her hair, it looks almost alive, And queries, using the & operator, return lines matching both queries: Executing query for: (hair & Alice) (hair & Alice) occurs 1 time (line 1) Alice Emma has long flowing red hair. Moreover, we want to be able to combine these operations, as in

55 fiery & bird wind We ll use normal C++ precedence rules ( 4.1.2, p. 136) to evaluate compound expressions such as this example. Thus, this query will match a line in which both fiery and bird appear or one in which wind appears: Executing Query for: ((fiery & bird) wind) ((fiery & bird) wind) occurs 3 times (line 2) Her Daddy says when the wind blows (line 4) like a fiery bird in flight. (line 5) A beautiful fiery bird, he tells her, Our output will print the query, using parentheses to indicate the way in which the query was interpreted. As with our original implementation, our system will display lines in ascending order and will not display the same line more than once An Object-Oriented Solution We might think that we should use the TextQuery class from (p. 487) to represent our word query and derive our other queries from that class. However, this design would be flawed. To see why, consider a Not query. A Word query looks for a particular word. In order for a Not query to be a kind of Word query, we would have to be able to identify the word for which the Not query was searching. In general, there is no such word. Instead, a Not query has a query (a Word query or any other kind of query) whose value it negates. Similarly, an And query and an Or query have two queries whose results it combines. This observation suggests that we model our different kinds of queries as independent classes that share a common base class: WordQuery // Daddy NotQuery // ~Alice OrQuery // hair Alice AndQuery // hair & Alice These classes will have only two operations: eval, which takes a TextQuery object and returns a QueryResult. The eval function will use the given TextQuery object to find the query s the matching lines. rep, which returns the string representation of the underlying query. This function will be used by eval to create a QueryResult representing the match and by the output operator to print the query expressions. Abstract Base Class

56 As we ve seen, our four query types are not related to one another by inheritance; they are conceptually siblings. Each class shares the same interface, which suggests that we ll need to define an abstract base class ( 15.4, p. 610) to represent that interface. We ll name our abstract base class Query_base, indicating that its role is to serve as the root of our query hierarchy. Our Query_base class will define eval and rep as pure virtual functions ( 15.4, p. 610). Each of our classes that represents a particular kind of query must override these functions. We ll derive WordQuery and NotQuery directly from Query_base. The AndQuery and OrQuery classes share one property that the other classes in our system do not: Each has two operands. To model this property, we ll define another abstract base class, named BinaryQuery, to represent queries with two operands. The AndQuery and OrQuery classes will inherit from BinaryQuery, which in turn will inherit from Query_base. These decisions give us the class design represented in Figure Figure Query_base Inheritance Hierarchy Key Concept: Inheritance versus Composition The design of inheritance hierarchies is a complicated topic in its own right and well beyond the scope of this language Primer. However, there is one important design guide that is so fundamental that every programmer should be familiar with it. When we define a class as publicly inherited from another, the derived class should reflect an Is A relationship to the base class. In well-designed class hierarchies, objects of a publicly derived class can be used wherever an object of the base class is expected. Another common relationship among types is a Has A relationship. Types related by a Has A relationship imply membership. In our bookstore example, our base class represents the concept of a quote for a book sold at a stipulated price. Our Bulk_quote is a kind of

57 quote, but one with a different pricing strategy. Our bookstore classes have a price and an ISBN. Hiding a Hierarchy in an Interface Class Our program will deal with evaluating queries, not with building them. However, we need to be able to create queries in order to run our program. The simplest way to do so is to write C++ expressions to create the queries. For example, we d like to generate the compound query previously described by writing code such as Query q = Query("fiery") & Query("bird") Query("wind"); This problem description implicitly suggests that user-level code won t use the inherited classes directly. Instead, we ll define an interface class named Query, which will hide the hierarchy. The Query class will store a pointer to Query_base. That pointer will be bound to an object of a type derived from Query_base. The Query class will provide the same operations as the Query_base classes: eval to evaluate the associated query, and rep to generate a string version of the query. It will also define an overloaded output operator to display the associated query. Users will create and manipulate Query_base objects only indirectly through operations on Query objects. We ll define three overloaded operators on Query objects, along with a Query constructor that takes a string. Each of these functions will dynamically allocate a new object of a type derived from Query_base: The & operator will generate a Query bound to a new AndQuery. The operator will generate a Query bound to a new OrQuery. The ~ operator will generate a Query bound to a new NotQuery. The Query constructor that takes a string will generate a new WordQuery. Understanding How These Classes Work It is important to realize that much of the work in this application consists of building objects to represent the user s query. For example, an expression such as the one above generates the collection of interrelated objects illustrated in Figure 15.3.

58 Figure Objects Created by Query Expressions Once the tree of objects is built up, evaluating (or generating the representation of) a query is basically a process (managed for us by the compiler) of following these links, asking each object to evaluate (or display) itself. For example, if we call eval on q (i.e., on the root of the tree), that call asks the OrQuery to which q points to eval itself. Evaluating this OrQuery calls eval on its two operands on the AndQuery and the WordQuery that looks for the word wind. Evaluating the AndQuery evaluates its two WordQuerys, generating the results for the words fiery and bird, respectively. When new to object-oriented programming, it is often the case that the hardest part in understanding a program is understanding the design. Once you are thoroughly comfortable with the design, the implementation flows naturally. As an aid to understanding this design, we ve summarized the classes used in this example in Table 15.1 (overleaf). Table Recap: Query Program Design

59 Exercises Section Exercise 15.31: Given that s1, s2, s3, and s4 are all strings, determine what objects are created in the following expressions: (a) Query(s1) Query(s2) & ~ Query(s3); (b) Query(s1) (Query(s2) & ~ Query(s3)); (c) (Query(s1) & (Query(s2)) (Query(s3) & Query(s4))); The Query_base and Query Classes We ll start our implementation by defining the Query_base class: // abstract class acts as a base class for concrete query types; all members are private class Query_base { friend class Query; protected:

Chapter 13: Copy Control. Overview. Overview. Overview

Chapter 13: Copy Control. Overview. Overview. Overview Chapter 13: Copy Control Overview The Copy Constructor The Assignment Operator The Destructor A Message-Handling Example Managing Pointer Members Each type, whether a built-in or class type, defines the

More information

Increases Program Structure which results in greater reliability. Polymorphism

Increases Program Structure which results in greater reliability. Polymorphism UNIT 4 C++ Inheritance What is Inheritance? Inheritance is the process by which new classes called derived classes are created from existing classes called base classes. The derived classes have all the

More information

VIRTUAL FUNCTIONS Chapter 10

VIRTUAL FUNCTIONS Chapter 10 1 VIRTUAL FUNCTIONS Chapter 10 OBJECTIVES Polymorphism in C++ Pointers to derived classes Important point on inheritance Introduction to virtual functions Virtual destructors More about virtual functions

More information

OBJECT ORIENTED PROGRAMMING USING C++ CSCI Object Oriented Analysis and Design By Manali Torpe

OBJECT ORIENTED PROGRAMMING USING C++ CSCI Object Oriented Analysis and Design By Manali Torpe OBJECT ORIENTED PROGRAMMING USING C++ CSCI 5448- Object Oriented Analysis and Design By Manali Torpe Fundamentals of OOP Class Object Encapsulation Abstraction Inheritance Polymorphism Reusability C++

More information

COMP6771 Advanced C++ Programming

COMP6771 Advanced C++ Programming 1 COMP6771 Advanced C++ Programming Week 11 Object Oriented Programming 2016 www.cse.unsw.edu.au/ cs6771 2 Covariants and Contravariants Let us assume that Class B is a subtype of class A. Covariants:

More information

POLYMORPHISM 2 PART. Shared Interface. Discussions. Abstract Base Classes. Abstract Base Classes and Pure Virtual Methods EXAMPLE

POLYMORPHISM 2 PART. Shared Interface. Discussions. Abstract Base Classes. Abstract Base Classes and Pure Virtual Methods EXAMPLE Abstract Base Classes POLYMORPHISM 2 PART Abstract Classes Static and Dynamic Casting Common Programming Errors class B { // base class virtual void m( ) =0; // pure virtual function class D1 : public

More information

POLYMORPHISM 2 PART Abstract Classes Static and Dynamic Casting Common Programming Errors

POLYMORPHISM 2 PART Abstract Classes Static and Dynamic Casting Common Programming Errors POLYMORPHISM 2 PART Abstract Classes Static and Dynamic Casting Common Programming Errors CSC 330 OO Software Design 1 Abstract Base Classes class B { // base class virtual void m( ) =0; // pure virtual

More information

C++ Programming: Polymorphism

C++ Programming: Polymorphism C++ Programming: Polymorphism 2018 년도 2 학기 Instructor: Young-guk Ha Dept. of Computer Science & Engineering Contents Run-time binding in C++ Abstract base classes Run-time type identification 2 Function

More information

Fast Introduction to Object Oriented Programming and C++

Fast Introduction to Object Oriented Programming and C++ Fast Introduction to Object Oriented Programming and C++ Daniel G. Aliaga Note: a compilation of slides from Jacques de Wet, Ohio State University, Chad Willwerth, and Daniel Aliaga. Outline Programming

More information

Ch. 12: Operator Overloading

Ch. 12: Operator Overloading Ch. 12: Operator Overloading Operator overloading is just syntactic sugar, i.e. another way to make a function call: shift_left(42, 3); 42

More information

Module 10 Inheritance, Virtual Functions, and Polymorphism

Module 10 Inheritance, Virtual Functions, and Polymorphism Module 10 Inheritance, Virtual Functions, and Polymorphism Table of Contents CRITICAL SKILL 10.1: Inheritance Fundamentals... 2 CRITICAL SKILL 10.2: Base Class Access Control... 7 CRITICAL SKILL 10.3:

More information

Absolute C++ Walter Savitch

Absolute C++ Walter Savitch Absolute C++ sixth edition Walter Savitch Global edition This page intentionally left blank Absolute C++, Global Edition Cover Title Page Copyright Page Preface Acknowledgments Brief Contents Contents

More information

QUIZ. What is wrong with this code that uses default arguments?

QUIZ. What is wrong with this code that uses default arguments? QUIZ What is wrong with this code that uses default arguments? Solution The value of the default argument should be placed in either declaration or definition, not both! QUIZ What is wrong with this code

More information

Data Structures (list, dictionary, tuples, sets, strings)

Data Structures (list, dictionary, tuples, sets, strings) Data Structures (list, dictionary, tuples, sets, strings) Lists are enclosed in brackets: l = [1, 2, "a"] (access by index, is mutable sequence) Tuples are enclosed in parentheses: t = (1, 2, "a") (access

More information

What are the characteristics of Object Oriented programming language?

What are the characteristics of Object Oriented programming language? What are the various elements of OOP? Following are the various elements of OOP:- Class:- A class is a collection of data and the various operations that can be performed on that data. Object- This is

More information

COMP6771 Advanced C++ Programming

COMP6771 Advanced C++ Programming 1.... COMP6771 Advanced C++ Programming Week 3 Part One: - Overview, and 2016 www.cse.unsw.edu.au/ cs6771 2.... What is Object-based Programming? A class uses data abstraction and encapsulation to define

More information

Object Oriented Programming

Object Oriented Programming OOP Object Oriented Programming Object 2 Object 1 Object 3 For : COP 3330. Object oriented Programming (Using C++) Object 4 http://www.compgeom.com/~piyush/teach/3330 Piyush Kumar Objects: State (fields),

More information

OOPS Viva Questions. Object is termed as an instance of a class, and it has its own state, behavior and identity.

OOPS Viva Questions. Object is termed as an instance of a class, and it has its own state, behavior and identity. OOPS Viva Questions 1. What is OOPS? OOPS is abbreviated as Object Oriented Programming system in which programs are considered as a collection of objects. Each object is nothing but an instance of a class.

More information

Lecture Topics. Administrivia

Lecture Topics. Administrivia ECE498SL Lec. Notes L8PA Lecture Topics overloading pitfalls of overloading & conversions matching an overloaded call miscellany new & delete variable declarations extensibility: philosophy vs. reality

More information

Short Notes of CS201

Short Notes of CS201 #includes: Short Notes of CS201 The #include directive instructs the preprocessor to read and include a file into a source code file. The file name is typically enclosed with < and > if the file is a system

More information

(12-1) OOP: Polymorphism in C++ D & D Chapter 12. Instructor - Andrew S. O Fallon CptS 122 (April 3, 2019) Washington State University

(12-1) OOP: Polymorphism in C++ D & D Chapter 12. Instructor - Andrew S. O Fallon CptS 122 (April 3, 2019) Washington State University (12-1) OOP: Polymorphism in C++ D & D Chapter 12 Instructor - Andrew S. O Fallon CptS 122 (April 3, 2019) Washington State University Key Concepts Polymorphism virtual functions Virtual function tables

More information

Overview of OOP. Dr. Zhang COSC 1436 Summer, /18/2017

Overview of OOP. Dr. Zhang COSC 1436 Summer, /18/2017 Overview of OOP Dr. Zhang COSC 1436 Summer, 2017 7/18/2017 Review Data Structures (list, dictionary, tuples, sets, strings) Lists are enclosed in square brackets: l = [1, 2, "a"] (access by index, is mutable

More information

CS201 - Introduction to Programming Glossary By

CS201 - Introduction to Programming Glossary By CS201 - Introduction to Programming Glossary By #include : The #include directive instructs the preprocessor to read and include a file into a source code file. The file name is typically enclosed with

More information

Instantiation of Template class

Instantiation of Template class Class Templates Templates are like advanced macros. They are useful for building new classes that depend on already existing user defined classes or built-in types. Example: stack of int or stack of double

More information

Polymorphism. Zimmer CSCI 330

Polymorphism. Zimmer CSCI 330 Polymorphism Polymorphism - is the property of OOP that allows the run-time binding of a function's name to the code that implements the function. (Run-time binding to the starting address of the code.)

More information

Cpt S 122 Data Structures. Course Review Midterm Exam # 2

Cpt S 122 Data Structures. Course Review Midterm Exam # 2 Cpt S 122 Data Structures Course Review Midterm Exam # 2 Nirmalya Roy School of Electrical Engineering and Computer Science Washington State University Midterm Exam 2 When: Monday (11/05) 12:10 pm -1pm

More information

step is to see how C++ implements type polymorphism, and this Exploration starts you on that journey.

step is to see how C++ implements type polymorphism, and this Exploration starts you on that journey. EXPLORATION 36 Virtual Functions Deriving classes is fun, but there s not a lot you can do with them at least, not yet. The next step is to see how C++ implements type polymorphism, and this Exploration

More information

PIC 10A Objects/Classes

PIC 10A Objects/Classes PIC 10A Objects/Classes Ernest Ryu UCLA Mathematics Last edited: November 13, 2017 User-defined types In C++, we can define our own custom types. Object is synonymous to variable, and class is synonymous

More information

The following is an excerpt from Scott Meyers new book, Effective C++, Third Edition: 55 Specific Ways to Improve Your Programs and Designs.

The following is an excerpt from Scott Meyers new book, Effective C++, Third Edition: 55 Specific Ways to Improve Your Programs and Designs. The following is an excerpt from Scott Meyers new book, Effective C++, Third Edition: 55 Specific Ways to Improve Your Programs and Designs. Item 9: Never call virtual functions during construction or

More information

QUIZ. How could we disable the automatic creation of copyconstructors

QUIZ. How could we disable the automatic creation of copyconstructors QUIZ How could we disable the automatic creation of copyconstructors pre-c++11? What syntax feature did C++11 introduce to make the disabling clearer and more permanent? Give a code example. QUIZ How

More information

C++ Inheritance and Encapsulation

C++ Inheritance and Encapsulation C++ Inheritance and Encapsulation Private and Protected members Inheritance Type Public Inheritance Private Inheritance Protected Inheritance Special method inheritance 1 Private Members Private members

More information

OOP THROUGH C++(R16) int *x; float *f; char *c;

OOP THROUGH C++(R16) int *x; float *f; char *c; What is pointer and how to declare it? Write the features of pointers? A pointer is a memory variable that stores the address of another variable. Pointer can have any name that is legal for other variables,

More information

What is Polymorphism? Quotes from Deitel & Deitel s. Why polymorphism? How? How? Polymorphism Part 1

What is Polymorphism? Quotes from Deitel & Deitel s. Why polymorphism? How? How? Polymorphism Part 1 Polymorphism Part 1 What is Polymorphism? Polymorphism refers to a programming language s ability to process objects differently depending on their data type or class. Number person real complex kid adult

More information

C++ Crash Kurs. Polymorphism. Dr. Dennis Pfisterer Institut für Telematik, Universität zu Lübeck

C++ Crash Kurs. Polymorphism. Dr. Dennis Pfisterer Institut für Telematik, Universität zu Lübeck C++ Crash Kurs Polymorphism Dr. Dennis Pfisterer Institut für Telematik, Universität zu Lübeck http://www.itm.uni-luebeck.de/people/pfisterer C++ Polymorphism Major abstractions of C++ Data abstraction

More information

1B1b Inheritance. Inheritance. Agenda. Subclass and Superclass. Superclass. Generalisation & Specialisation. Shapes and Squares. 1B1b Lecture Slides

1B1b Inheritance. Inheritance. Agenda. Subclass and Superclass. Superclass. Generalisation & Specialisation. Shapes and Squares. 1B1b Lecture Slides 1B1b Inheritance Agenda Introduction to inheritance. How Java supports inheritance. Inheritance is a key feature of object-oriented oriented programming. 1 2 Inheritance Models the kind-of or specialisation-of

More information

static CS106L Spring 2009 Handout #21 May 12, 2009 Introduction

static CS106L Spring 2009 Handout #21 May 12, 2009 Introduction CS106L Spring 2009 Handout #21 May 12, 2009 static Introduction Most of the time, you'll design classes so that any two instances of that class are independent. That is, if you have two objects one and

More information

QUIZ. How could we disable the automatic creation of copyconstructors

QUIZ. How could we disable the automatic creation of copyconstructors QUIZ How could we disable the automatic creation of copyconstructors pre-c++11? What syntax feature did C++11 introduce to make the disabling clearer and more permanent? Give a code example. Ch. 14: Inheritance

More information

Polymorphism Part 1 1

Polymorphism Part 1 1 Polymorphism Part 1 1 What is Polymorphism? Polymorphism refers to a programming language s ability to process objects differently depending on their data type or class. Number person real complex kid

More information

Introduction Of Classes ( OOPS )

Introduction Of Classes ( OOPS ) Introduction Of Classes ( OOPS ) Classes (I) A class is an expanded concept of a data structure: instead of holding only data, it can hold both data and functions. An object is an instantiation of a class.

More information

OBJECT ORIENTED PROGRAMMING USING C++

OBJECT ORIENTED PROGRAMMING USING C++ OBJECT ORIENTED PROGRAMMING USING C++ Overview of C++ Overloading Overloading occurs when the same operator or function name is used with different signatures Both operators and functions can be overloaded

More information

Introduction to C++ Introduction to C++ Dr Alex Martin 2013 Slide 1

Introduction to C++ Introduction to C++ Dr Alex Martin 2013 Slide 1 Introduction to C++ Introduction to C++ Dr Alex Martin 2013 Slide 1 Inheritance Consider a new type Square. Following how we declarations for the Rectangle and Circle classes we could declare it as follows:

More information

Why use inheritance? The most important slide of the lecture. Programming in C++ Reasons for Inheritance (revision) Inheritance in C++

Why use inheritance? The most important slide of the lecture. Programming in C++ Reasons for Inheritance (revision) Inheritance in C++ Session 6 - Inheritance in C++ The most important slide of the lecture Dr Christos Kloukinas City, UoL http://staff.city.ac.uk/c.kloukinas/cpp (slides originally produced by Dr Ross Paterson) Why use inheritance?

More information

G52CPP C++ Programming Lecture 13

G52CPP C++ Programming Lecture 13 G52CPP C++ Programming Lecture 13 Dr Jason Atkin http://www.cs.nott.ac.uk/~jaa/cpp/ g52cpp.html 1 Last lecture Function pointers Arrays of function pointers Virtual and non-virtual functions vtable and

More information

CS102: Variables and Expressions

CS102: Variables and Expressions CS102: Variables and Expressions The topic of variables is one of the most important in C or any other high-level programming language. We will start with a simple example: int x; printf("the value of

More information

CS304 Object Oriented Programming Final Term

CS304 Object Oriented Programming Final Term 1. Which of the following is the way to extract common behaviour and attributes from the given classes and make a separate class of those common behaviours and attributes? Generalization (pg 29) Sub-typing

More information

Cpt S 122 Data Structures. Introduction to C++ Part II

Cpt S 122 Data Structures. Introduction to C++ Part II Cpt S 122 Data Structures Introduction to C++ Part II Nirmalya Roy School of Electrical Engineering and Computer Science Washington State University Topics Objectives Defining class with a member function

More information

Java Threads and intrinsic locks

Java Threads and intrinsic locks Java Threads and intrinsic locks 1. Java and OOP background fundamentals 1.1. Objects, methods and data One significant advantage of OOP (object oriented programming) is data encapsulation. Each object

More information

explicit class and default definitions revision of SC22/WG21/N1582 =

explicit class and default definitions revision of SC22/WG21/N1582 = Doc No: SC22/WG21/ N1702 04-0142 Project: JTC1.22.32 Date: Wednesday, September 08, 2004 Author: Francis Glassborow & Lois Goldthwaite email: francis@robinton.demon.co.uk explicit class and default definitions

More information

2 ADT Programming User-defined abstract data types

2 ADT Programming User-defined abstract data types Preview 2 ADT Programming User-defined abstract data types user-defined data types in C++: classes constructors and destructors const accessor functions, and inline functions special initialization construct

More information

Object Oriented Programming. Assistant Lecture Omar Al Khayat 2 nd Year

Object Oriented Programming. Assistant Lecture Omar Al Khayat 2 nd Year Object Oriented Programming Assistant Lecture Omar Al Khayat 2 nd Year Syllabus Overview of C++ Program Principles of object oriented programming including classes Introduction to Object-Oriented Paradigm:Structures

More information

American National Standards Institute Reply to: Josee Lajoie

American National Standards Institute Reply to: Josee Lajoie Accredited Standards Committee X3 Information Processing Systems Operating under the procedures of American National Standards Institute Doc No: X3J16/95-0051 WG21/N0651 Date: March 3, 1995 Page 1 of 15

More information

The pre-processor (cpp for C-Pre-Processor). Treats all # s. 2 The compiler itself (cc1) this one reads text without any #include s

The pre-processor (cpp for C-Pre-Processor). Treats all # s. 2 The compiler itself (cc1) this one reads text without any #include s Session 2 - Classes in C++ Dr Christos Kloukinas City, UoL http://staff.city.ac.uk/c.kloukinas/cpp (slides originally produced by Dr Ross Paterson) A C++ source file may contain: include directives #include

More information

COMP322 - Introduction to C++ Lecture 09 - Inheritance continued

COMP322 - Introduction to C++ Lecture 09 - Inheritance continued COMP322 - Introduction to C++ Lecture 09 - Inheritance continued Dan Pomerantz School of Computer Science 11 March 2012 Recall from last time Inheritance describes the creation of derived classes from

More information

These are notes for the third lecture; if statements and loops.

These are notes for the third lecture; if statements and loops. These are notes for the third lecture; if statements and loops. 1 Yeah, this is going to be the second slide in a lot of lectures. 2 - Dominant language for desktop application development - Most modern

More information

Client Code - the code that uses the classes under discussion. Coupling - code in one module depends on code in another module

Client Code - the code that uses the classes under discussion. Coupling - code in one module depends on code in another module Basic Class Design Goal of OOP: Reduce complexity of software development by keeping details, and especially changes to details, from spreading throughout the entire program. Actually, the same goal as

More information

CSCI 102 Fall 2010 Exam #1

CSCI 102 Fall 2010 Exam #1 Name: USC Username: CSCI 102 Fall 2010 Exam #1 Problems Problem #1 (14 points) Problem #2 (15 points) Problem #3 (20 points) Problem #4 (16 points) Problem #5 (35 points) Total (100 points) Problem 1 Short

More information

QUIZ. Write the following for the class Bar: Default constructor Constructor Copy-constructor Overloaded assignment oper. Is a destructor needed?

QUIZ. Write the following for the class Bar: Default constructor Constructor Copy-constructor Overloaded assignment oper. Is a destructor needed? QUIZ Write the following for the class Bar: Default constructor Constructor Copy-constructor Overloaded assignment oper. Is a destructor needed? Or Foo(x), depending on how we want the initialization

More information

C++ Important Questions with Answers

C++ Important Questions with Answers 1. Name the operators that cannot be overloaded. sizeof,.,.*,.->, ::,? 2. What is inheritance? Inheritance is property such that a parent (or super) class passes the characteristics of itself to children

More information

Data Abstraction. Hwansoo Han

Data Abstraction. Hwansoo Han Data Abstraction Hwansoo Han Data Abstraction Data abstraction s roots can be found in Simula67 An abstract data type (ADT) is defined In terms of the operations that it supports (i.e., that can be performed

More information

CE221 Programming in C++ Part 1 Introduction

CE221 Programming in C++ Part 1 Introduction CE221 Programming in C++ Part 1 Introduction 06/10/2017 CE221 Part 1 1 Module Schedule There are two lectures (Monday 13.00-13.50 and Tuesday 11.00-11.50) each week in the autumn term, and a 2-hour lab

More information

Fundamental Concepts and Definitions

Fundamental Concepts and Definitions Fundamental Concepts and Definitions Identifier / Symbol / Name These terms are synonymous: they refer to the name given to a programming component. Classes, variables, functions, and methods are the most

More information

Intro. Scheme Basics. scm> 5 5. scm>

Intro. Scheme Basics. scm> 5 5. scm> Intro Let s take some time to talk about LISP. It stands for LISt Processing a way of coding using only lists! It sounds pretty radical, and it is. There are lots of cool things to know about LISP; if

More information

C++ Classes 2008/04/03. Programming Research Laboratory Seoul National University

C++ Classes 2008/04/03. Programming Research Laboratory Seoul National University C++ Classes 2008/04/03 Soonho Kong soon@ropas.snu.ac.kr Programming Research Laboratory Seoul National University 1 Most of text and examples are excerpted from C++ Primer 4 th e/d. 2 Contents 12.1 Class

More information

Virtual functions concepts

Virtual functions concepts Virtual functions concepts l Virtual: exists in essence though not in fact l Idea is that a virtual function can be used before it is defined And it might be defined many, many ways! l Relates to OOP concept

More information

Software Design and Analysis for Engineers

Software Design and Analysis for Engineers Software Design and Analysis for Engineers by Dr. Lesley Shannon Email: lshannon@ensc.sfu.ca Course Website: http://www.ensc.sfu.ca/~lshannon/courses/ensc251 Simon Fraser University Slide Set: 2 Date:

More information

Copy Control 2008/04/08. Programming Research Laboratory Seoul National University

Copy Control 2008/04/08. Programming Research Laboratory Seoul National University Copy Control 2008/04/08 Soonho Kong soon@ropas.snu.ac.kr Programming Research Laboratory Seoul National University 1 Most of text and examples are excerpted from C++ Primer 4 th e/d. 2 Types control what

More information

Part X. Advanced C ++

Part X. Advanced C ++ Part X Advanced C ++ topics Philip Blakely (LSC) Advanced C++ 158 / 217 References The following are highly regarded books. They are fairly in-depth, and I haven t read them in their entirity. However,

More information

Typed Racket: Racket with Static Types

Typed Racket: Racket with Static Types Typed Racket: Racket with Static Types Version 5.0.2 Sam Tobin-Hochstadt November 6, 2010 Typed Racket is a family of languages, each of which enforce that programs written in the language obey a type

More information

15: Polymorphism & Virtual Functions

15: Polymorphism & Virtual Functions 15: Polymorphism & Virtual Functions 김동원 2003.02.19 Overview virtual function & constructors Destructors and virtual destructors Operator overloading Downcasting Thinking in C++ Page 1 virtual functions

More information

Tokens, Expressions and Control Structures

Tokens, Expressions and Control Structures 3 Tokens, Expressions and Control Structures Tokens Keywords Identifiers Data types User-defined types Derived types Symbolic constants Declaration of variables Initialization Reference variables Type

More information

Object-Oriented Programming, Iouliia Skliarova

Object-Oriented Programming, Iouliia Skliarova Object-Oriented Programming, Iouliia Skliarova You reuse code by creating new classes, but instead of creating them from scratch, you use existing classes that someone else has built and debugged. In composition

More information

I m sure you have been annoyed at least once by having to type out types like this:

I m sure you have been annoyed at least once by having to type out types like this: Type Inference The first thing I m going to talk about is type inference. C++11 provides mechanisms which make the compiler deduce the types of expressions. These features allow you to make your code more

More information

Welcome to Teach Yourself Acknowledgments Fundamental C++ Programming p. 2 An Introduction to C++ p. 4 A Brief History of C++ p.

Welcome to Teach Yourself Acknowledgments Fundamental C++ Programming p. 2 An Introduction to C++ p. 4 A Brief History of C++ p. Welcome to Teach Yourself p. viii Acknowledgments p. xv Fundamental C++ Programming p. 2 An Introduction to C++ p. 4 A Brief History of C++ p. 6 Standard C++: A Programming Language and a Library p. 8

More information

Software Engineering /48

Software Engineering /48 Software Engineering 1 /48 Topics 1. The Compilation Process and You 2. Polymorphism and Composition 3. Small Functions 4. Comments 2 /48 The Compilation Process and You 3 / 48 1. Intro - How do you turn

More information

IT 374 C# and Applications/ IT695 C# Data Structures

IT 374 C# and Applications/ IT695 C# Data Structures IT 374 C# and Applications/ IT695 C# Data Structures Module 2.5: Methods A Deeper Look Xianrong (Shawn) Zheng Spring 2017 1 Outline static Methods, static Variables, and Class Math Methods with Multiple

More information

C++ Inheritance and Encapsulation

C++ Inheritance and Encapsulation C++ Inheritance and Encapsulation Protected members Inheritance Type Public Inheritance Private Inheritance Protected Inheritance Special method inheritance Private vs. Protected Private: private members

More information

Introduction to Computers and C++ Programming p. 1 Computer Systems p. 2 Hardware p. 2 Software p. 7 High-Level Languages p. 8 Compilers p.

Introduction to Computers and C++ Programming p. 1 Computer Systems p. 2 Hardware p. 2 Software p. 7 High-Level Languages p. 8 Compilers p. Introduction to Computers and C++ Programming p. 1 Computer Systems p. 2 Hardware p. 2 Software p. 7 High-Level Languages p. 8 Compilers p. 9 Self-Test Exercises p. 11 History Note p. 12 Programming and

More information

Basic memory model Using functions Writing functions. Basics Prototypes Parameters Return types Functions and memory Names and namespaces

Basic memory model Using functions Writing functions. Basics Prototypes Parameters Return types Functions and memory Names and namespaces Basic memory model Using functions Writing functions Basics Prototypes Parameters Return types Functions and memory Names and namespaces When a program runs it requires main memory (RAM) space for Program

More information

Contents. I. Classes, Superclasses, and Subclasses. Topic 04 - Inheritance

Contents. I. Classes, Superclasses, and Subclasses. Topic 04 - Inheritance Contents Topic 04 - Inheritance I. Classes, Superclasses, and Subclasses - Inheritance Hierarchies Controlling Access to Members (public, no modifier, private, protected) Calling constructors of superclass

More information

Lecture 13: more class, C++ memory management

Lecture 13: more class, C++ memory management CIS 330: / / / / (_) / / / / _/_/ / / / / / \/ / /_/ / `/ \/ / / / _/_// / / / / /_ / /_/ / / / / /> < / /_/ / / / / /_/ / / / /_/ / / / / / \ /_/ /_/_/_/ _ \,_/_/ /_/\,_/ \ /_/ \ //_/ /_/ Lecture 13:

More information

The Dynamic Typing Interlude

The Dynamic Typing Interlude CHAPTER 6 The Dynamic Typing Interlude In the prior chapter, we began exploring Python s core object types in depth with a look at Python numbers. We ll resume our object type tour in the next chapter,

More information

Object-Oriented Programming

Object-Oriented Programming - oriented - iuliana@cs.ubbcluj.ro Babes-Bolyai University 2018 1 / 56 Overview - oriented 1 2 -oriented 3 4 5 6 7 8 Static and friend elements 9 Summary 2 / 56 I - oriented was initially created by Bjarne

More information

Midterm 2. 7] Explain in your own words the concept of a handle class and how it s implemented in C++: What s wrong with this answer?

Midterm 2. 7] Explain in your own words the concept of a handle class and how it s implemented in C++: What s wrong with this answer? Midterm 2 7] Explain in your own words the concept of a handle class and how it s implemented in C++: What s wrong with this answer? A handle class is a pointer vith no visible type. What s wrong with

More information

CSE 374 Programming Concepts & Tools. Hal Perkins Spring 2010

CSE 374 Programming Concepts & Tools. Hal Perkins Spring 2010 CSE 374 Programming Concepts & Tools Hal Perkins Spring 2010 Lecture 19 Introduction ti to C++ C++ C++ is an enormous language: g All of C Classes and objects (kind of like Java, some crucial differences)

More information

Problem Solving with C++

Problem Solving with C++ GLOBAL EDITION Problem Solving with C++ NINTH EDITION Walter Savitch Kendrick Mock Ninth Edition PROBLEM SOLVING with C++ Problem Solving with C++, Global Edition Cover Title Copyright Contents Chapter

More information

Modern C++ for Computer Vision and Image Processing. Igor Bogoslavskyi

Modern C++ for Computer Vision and Image Processing. Igor Bogoslavskyi Modern C++ for Computer Vision and Image Processing Igor Bogoslavskyi Outline Move semantics Classes Operator overloading Making your class copyable Making your class movable Rule of all or nothing Inheritance

More information

1 Terminology. 2 Environments and Static Scoping. P. N. Hilfinger. Fall Static Analysis: Scope and Types

1 Terminology. 2 Environments and Static Scoping. P. N. Hilfinger. Fall Static Analysis: Scope and Types and Computer Sciences Computer Science Division CS 164 Fall 2006 P. N. Hilfinger Static Analysis: Scope and Types 1 Terminology Programs, in general, are simply collections of definitions of terms, which

More information

Operator overloading

Operator overloading 1 Introduction 2 The copy constructor 3 Operator Overloading 4 Eg 1: Adding two vectors 5 The -> operator 6 The this pointer 7 Overloading = 8 Unary operators 9 Overloading for the matrix class 10 The

More information

Type Checking and Type Equality

Type Checking and Type Equality Type Checking and Type Equality Type systems are the biggest point of variation across programming languages. Even languages that look similar are often greatly different when it comes to their type systems.

More information

Week 8: Operator overloading

Week 8: Operator overloading Due to various disruptions, we did not get through all the material in the slides below. CS319: Scientific Computing (with C++) Week 8: Operator overloading 1 The copy constructor 2 Operator Overloading

More information

In fact, as your program grows, you might imagine it organized by class and superclass, creating a kind of giant tree structure. At the base is the

In fact, as your program grows, you might imagine it organized by class and superclass, creating a kind of giant tree structure. At the base is the 6 Method Lookup and Constant Lookup As we saw in Chapter 5, classes play an important role in Ruby, holding method definitions and constant values, among other things. We also learned how Ruby implements

More information

OOPs Concepts. 1. Data Hiding 2. Encapsulation 3. Abstraction 4. Is-A Relationship 5. Method Signature 6. Polymorphism 7. Constructors 8.

OOPs Concepts. 1. Data Hiding 2. Encapsulation 3. Abstraction 4. Is-A Relationship 5. Method Signature 6. Polymorphism 7. Constructors 8. OOPs Concepts 1. Data Hiding 2. Encapsulation 3. Abstraction 4. Is-A Relationship 5. Method Signature 6. Polymorphism 7. Constructors 8. Type Casting Let us discuss them in detail: 1. Data Hiding: Every

More information

by Pearson Education, Inc. All Rights Reserved.

by Pearson Education, Inc. All Rights Reserved. Let s improve the bubble sort program of Fig. 6.15 to use two functions bubblesort and swap. Function bubblesort sorts the array. It calls function swap (line 51) to exchange the array elements array[j]

More information

The Typed Racket Guide

The Typed Racket Guide The Typed Racket Guide Version 5.3.6 Sam Tobin-Hochstadt and Vincent St-Amour August 9, 2013 Typed Racket is a family of languages, each of which enforce

More information

C++ for Java Programmers

C++ for Java Programmers Basics all Finished! Everything we have covered so far: Lecture 5 Operators Variables Arrays Null Terminated Strings Structs Functions 1 2 45 mins of pure fun Introduction Today: Pointers Pointers Even

More information

Override Control Using Contextual Keywords

Override Control Using Contextual Keywords Doc No: SC22/WG21/N3163 = PL22.16/10-0153 Date: 2010-10-10 Project: Reply to: JTC1.22.32 Herb Sutter Microsoft Corp. 1 Microsoft Way Redmond WA USA 98052 Email: hsutter@microsoft.com Override Control Using

More information

CPS 506 Comparative Programming Languages. Programming Language

CPS 506 Comparative Programming Languages. Programming Language CPS 506 Comparative Programming Languages Object-Oriented Oriented Programming Language Paradigm Introduction Topics Object-Oriented Programming Design Issues for Object-Oriented Oriented Languages Support

More information

mywbut.com Exception Handling

mywbut.com Exception Handling Exception Handling An exception is a run-time error. Proper handling of exceptions is an important programming issue. This is because exceptions can and do happen in practice and programs are generally

More information

Declaration Syntax. Declarations. Declarators. Declaration Specifiers. Declaration Examples. Declaration Examples. Declarators include:

Declaration Syntax. Declarations. Declarators. Declaration Specifiers. Declaration Examples. Declaration Examples. Declarators include: Declarations Based on slides from K. N. King Declaration Syntax General form of a declaration: declaration-specifiers declarators ; Declaration specifiers describe the properties of the variables or functions

More information

CSE 303: Concepts and Tools for Software Development

CSE 303: Concepts and Tools for Software Development CSE 303: Concepts and Tools for Software Development Hal Perkins Autumn 2008 Lecture 24 Introduction to C++ CSE303 Autumn 2008, Lecture 24 1 C++ C++ is an enormous language: All of C Classes and objects

More information