What & How & Why

Chapter.15

Answers for chapter 15


  • Quote class implementation routine

Ex.15.1-15.10

ex.15.1
Exercise 15.1: What is a virtual member?

Virtual members are those members who expect their derived classes to override.

ex.15.2
Exercise 15.2: How does the protected access specifier differ from private?
  • Private members are accessible from their class members, but not from their derived class members.
  • Protected members are accessible from both the base class members and the derived class members.
ex.15.3
Exercise 15.3: Define your own versions of the Quote class and the print_total function.

CODE : header source

ex.15.4
Exercise 15.4: Which of the following declarations, if any, are incorrect? Explain why.

class Base { ... };

//error, a class cannot be derived from itself
(a) class Derived : public Derived { ... };

//ok
(b) class Derived : private Base { ... };

//error, the declaration of a derived class must not contain the derived list
(c) class Derived : public Base;

ex.15.5
Exercise 15.5: Define your own version of the Bulk_quote class.

CODE : header source test file

ex.15.6
Exercise 15.6: Test your print_total function from the exercises in § 15.2.1 (p. 595) by passing both Quote and Bulk_quote objects o that function.

Please check the code in exercise 15.5.

ex.15.7
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.

CODE : header source test file

ex.15.8
Exercise 15.8: Define static type and dynamic type.
  • The static type of an expression is always known at 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.
ex.15.9
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.

A dynamic type of an expression differs from a static type based on the type of the argument to which its corresponding parameter is bound. Assuming we have a base class Base and a derived class Derived, the following binding might cause the dynamic type of the expression to differ from its static type:

  • Binding Derived type object to an Base& parameter
  • Binding Derived type object to an Base* parameter
  • Binding Derived* to Base*, or Derived& to Base&
ex.15.10
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.
  1. read function takes istream& type parameter.
  2. we supplied an ifstream& type object as the argument.
  3. Because ifstream is a derived class of istream, the above binding will bind the base portion of the ifstream& object to the parameter, satisfying the requirement of read.
ex.15.11
Exercise 15.11: Add a virtual debug function to your Quote class hierarchy that displays the data members of the respective classes.

CODE : header source test file

ex.15.12
Exercise 15.12: Is it ever useful to declare a member function as both override and final? Why or why not?

Yes. A final function means that subsequent classes cannot override it, although the function can still override the function it inherited.

ex.15.13
Exercise 15.13: Given the following classes, explain each print function. If there is a problem in this code, how would you fix it?

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; } //error, infinite recursive
private:
   int i;
};

  • The base::print() will print its basename member
  • The derived::print() will print its basename and j member. However, the call in the example will result in an infinite recursive error since print(os) was supposed to call the base class version of print(), but it failed to circumvent the virtual mechanism. The problem can be fixed by specifying the scope of the call:

void print(ostream &os) { base::print(os); os << " " << i; }

ex.15.14
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(); //base::print(), compile time
b) dobj.print(); //derived::print(), compile time
c) bp1->name(); //base::print(), compile time
d) bp2->name(); //base::print(), compile time
e) br1.print(); //base::print(), run time
f) br2.print(); //derived::print(), run time

ex.15.15
Exercise 15.15: Define your own versions of Disc_quote and Bulk_quote.

CODE : header source test file

ex.15.16
Exercise 15.16: Rewrite the class representing a limited discount strategy, which you wrote for the exercises in § 15.2.2 (p. 601), to inherit from Disc_quote.

CODE : header source test file

ex.15.17
Exercise 15.17: Try to define an object of type Disc_quote and see what errors you get from the compiler.

ex_15_16_test.cc: In function ‘int main(int, const char**)’:
ex_15_16_test.cc:20:13: error: cannot declare variable ‘dq’ to be of abstract type ‘Disc_quote’
   20 |  Disc_quote dq;

ex.15.18
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 allowed:

//  d1 has type Pub_Derv, legal, public inheritance, derived-to-base conversion preformed by user is allowed.
Base *p = &d1; 

//  d2 has type Priv_Derv, illegal, private inheritance, derived-to-base conversion preformed by user is not allowed.
p = &d2;   

//  d3 has type Prot_Derv, illegal, protected inheritance, only member and friends of the class are allowed to preform derived-to-base conversion.
p = &d3;    

//  dd1 has type Derived_from_Public, legal, same reason as the d1
p = &dd1;  

//  dd2 has type Derived_from_Private, illegal, user prefromed derived-to-base conversion is not allowed either in private or proteceted inheritance   
p = &dd2;      

//  dd3 has type Derived_from_Protected, illegal, user prefromed derived-to-base conversion is not allowed either in private or proteceted inheritance

p = &dd3;

ex.15.19
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; }
Assuming D inherits from B:

  • Pub_Derv, legal, Members and friends of D can preform the derived-to-base conversion regardless of how derived class inherits from the B.
  • Priv_Derv, legal, the same resaon as the Pub_derv
  • Prot_Derv, legal, the same resaon as the Pub_derv
  • Derived_from_Public, legal, the derived class of D may preform the derived-to-base conversion if D inherits from B either public or protected.
  • Derived_from_Private, illegal, if D inherits privately from B, such code may not use the conversion.
  • Derived_from_Protected, legal, the same reason as Derived_from_Public.
ex.15.20

Exercise 15.20: Write code to test your answers to the previous two exercises.

###Test in ex.15.18
#p = &prod;
#ex_15_20.cc:23:7: error: ‘Base’ is an inaccessible base of ‘Prot_Derv’

#p = &prid;
#ex_15_20.cc:25:7: error: ‘Base’ is an inaccessible base of ‘Priv_Derv’

#p = &dfprod;
#ex_15_20.cc:31:7: error: ‘Base’ is an inaccessible base of ‘Derived_from_protected’

#p = &dfprid;
#ex_15_20.cc:32:7: error: ‘Base’ is an inaccessible base of ‘Derived_from_private’

###Test in ex.15.19
#ex_15_20.h: In member function ‘void Derived_from_private::memfcn(Base&)’:
#ex_15_20.h:58:30: error: ‘Base’ is an inaccessible base of ‘Derived_from_private’
CODE : header test file

Ex.15.21-15.30

ex.15.21
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)

<html>

<img src=“/_media/programming/cpp/cpp_primer/answers/shape_heirarchy_1_.svg” width=“500”>

</html>

CODE : header

ex.15.22
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.

CODE : header

ex.15.23
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?

CODE : header test

Please check the comments in the test file to see how the calls will be resolved.

Alternative way: using using Base::fcn to make Base::fcn avaliable to D1,then overwrite it:

class D1 : public Base {
public:
    using Base::fcn;
    int fcn() override { //...// };
    int fcn(int);      // parameter list differs from fcn in Base
    virtual void f2(); // new virtual function that does not exist in Base
};

ex.15.24
Exercise 15.24: What kinds of classes need a virtual destructor? What operations must a virtual destructor perform?

A base class almost always needs a destructor, so that it can make the destructor virtual. There may be cases in which we need to use virtual destructors when derived classes have dynamically allocated resources.

ex.15.25
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?

This how we define Disc_quote:

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
};
Because Disc_quote() also defines a constructor that takes four arguments, the compiler will not synthesize a default constructor for the class. Therefore, if we didn't define a default constructor in the Disc_quote, the Bluk_quote class won't be able to initialize its object since the default constructor in its direct base class isn't accessible.

ex.15.26
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.

header source test file

ex.15.27
Exercise 15.27: Redefine your Bulk_quote class to inherit its constructors.

header source test file

ex.15.28
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.

source

output:

The total should be: 105
The total after a sliced down: 150

ex.15.29
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.

source

output:

The total should be: 105
The total with no sliced down: 105
The smart pointer version works because the derived-to-base conversion is performed when calling the member netPrice(). When we store smart pointers into the vector, their type changes from shard_ptr<Bulk_quote> to shared_ptr<Quote>. Polymofisim, however, enables us to access the derived version of the virtual function netPrice(), via reference or pointer to the base object. In this case, even though our smart pointers have their type changed, they still point to these objects of the Bulk_quote type. When we use these smart pointers to call the netPrice() member they will be bound to the Bulk_quote objects. As a result, the member netPrice() we called from these pointers is actually Bulk_quote::netPrice(), which give us the correct result.

ex.15.30
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.

header source test file

Ex.15.31-15.40

ex.15.31
Exercise 15.31: Given that s1, s2, s3, and s4 are all strings, determine what objects are created in the following expressions:

(1) Query(s1) | Query(s2) & ~ Query(s3);
(2) Query(s1) | (Query(s2) & ~ Query(s3));
(3) (Query(s1) & (Query(s2)) | (Query(s3) & Query(s4)));


<html>

<img src=“/_media/programming/cpp/cpp_primer/answers/15_31_ans.svg” width=“450”>

</html>

ex.15.32
Exercise 15.32: What happens when an object of type Query is copied, moved, assigned, and destroyed?

Query class manages objects with Query_base type via smart_pointer. Whenever we perform a copy control behavior over a Query-type object, we are modifying the reference counts of the smart pointer to which the object is bound. Therefore, depending on which operation is performed on the Query object, the reference counts of the smart pointer to which the object is pointed may go up, down, or even to zero and causing the Query_base object to be freed.

ex.15.33
Exercise 15.33: What about objects of type Query_base?

Since Query_base is an abstract class, there is no concrete object that can be constructed from it. Query_base's derived class objects are managed by the synthesized copy control members.

ex.15.34
Exercise 15.34: For the expression built in Figure 15.3 (p. 638):

(a) List the constructors executed in processing that expression.
(b) List the calls to rep that are made from cout << q.
(c) List the calls to eval made from q.eval().
a):

<html>

<img src=“/_media/programming/cpp/cpp_primer/answers/15_35_answer_1.svg” >

</html>

b):
<html>

<img src=“/_media/programming/cpp/cpp_primer/answers/15_34_answer_2.svg” width = 450“>

</html>

b):
<html>

<img src=”/_media/programming/cpp/cpp_primer/answers/15_34_answer_3.svg“ width = 450”>

</html>

ex.15.35
Exercise 15.35: Implement the Query and Query_base classes, including a definition of rep but omitting the definition of eval.

header source test file

ex.15.36
Exercise 15.36: Put print statements in the constructors and rep members and run your code to check your answers to (a) and (b) from the first exercise.

/* output of statement a */
WordQuery CSTR
Query CSTR
WordQuery CSTR
Query CSTR
WordQuery CSTR
Query CSTR
Query Copy CSTR
Query Copy CSTR
BinaryQuery CSTR
AndQuery CSTR
Query shard_ptr CSTR
Query Copy CSTR
Query Copy CSTR
BinaryQuery CSTR
OrQuery CSTR
Query shard_ptr CSTR

/* output of statement b */
Query::rep()
BinaryQuery::rep()
Query::rep()
WordQuery::rep()
Query::rep()
BinaryQuery::rep()
Query::rep()
WordQuery::rep()
Query::rep()
WordQuery::rep()

ex.15.37
Exercise 15.37: What changes would your classes need if the derived classes had members of type shared_ptr<Query_base> rather than of type Query?

A reason for storing Shared_ptr<Query_base> smart pointers in the Query class is that it facilitates classes management. For example, relationship operations only need an interface to access the resource, and the dynamic binding decides which concrete method is needed.

In this program, we can see how the & / | / ~ operations are advantageous. By letting the derived classes hold a shard_ptr<Query_base> smart pointer instead (supposing each concrete derived class stores its own smart pointer), the Query class cannot choose the right class type for us anymore. In other words, we have to manually raise the concrete call and implement overloaded versions for each concrete class. As a result, The whole process will be complexed, not just in querying, but also in implementing. For instance, the equivalent querying of the following example would be like:

/* using Query as an interface */
Query(s1) & Query(s2) | Query(s3);

/* equivalent if we storing the smart pointer in each derived classes */
WordQuery(s1) & WordQuery(s2) | WordQuery(s3);

Besides, the | operation must define a version that takes AndQuery and WordQuery parameters, in order to process the call above:

operator&(const WordQuery& lhs, const WordQuery& rhs); //returns a smart pointer, points to an AndQuery Object
operator|(const AndQuery & lhs, const WordQuery & rhs); // returns a smart pointer, points to an OrQuery Object

ex.15.38
Are the following declarations legal? If not, why not? If so, explain what the declarations mean.

// illegal. BinaryQuery is an abstract class that cannot be instanced.
BinaryQuery a = Query("fiery") & Query("bird");

/* The constructors in the following classes cannot be called by users since they are private.   */

// illegal. the ''&'' operation will returns an smart pointer that manage a AndQuery object; However, AndQuery do not have any constructor that takes a smart pointer as the parameter.
AndQuery b = Query("fiery") & Query("bird");

//illegal. same reason as the above
OrQuery c = Query("fiery") & Query("bird");

ex.15.39
Exercise 15.39: Implement the Query and Query_base classes. Test your application by evaluating and printing a query such as the one in Figure 15.3 (p. 638).

header source test file

ex.15.40
Exercise 15.40: In the OrQuery eval function what would happen if its rhs member returned an empty set? What if its lhs member did so? What if both rhs and lhs returned empty sets?

OrQuery::eval() uses set::inerst() to implement set union operation. Thus, regardless of which operand returns an empty line number set, the final result will be determined by the other. In the event that both return an empty number set, no search result will be returned.

Ex.15.41-15.42

ex.15.41
Exercise 15.41: Reimplement your classes to use built-in pointers to Query_base rather than shared_ptrs. Remember that your classes will no longer be able to use the synthesized copy-control members.

header source test file

ex.15.42
Exercise 15.42: Design and implement one of the following enhancements:
a) Print words only once per sentence rather than once per line.
b) Introduce a history system in which the user can refer to a previous query by number, possibly adding to it or combining it with another.
c) Allow the user to limit the results so that only matches in a given range of lines are displayed.

(a) Print words only once per sentence rather than once per line.
header source test file

Some thoughts about the other two questions:

  • b) a new class that store the history records is not hard to implement; a vector should handle it well. The main problem is how to interpret the input string and rewrite it to commands. That may need a phaser; will come back later to do it.
  • c) we can limited the output range of the line number to get the result.