What is a virtual member?
A member function should be dynamically bound by preceding its declaration.
A virtual member in a base class expects its derived class define its own version. In particular base classes ordinarily should define a virtual destructor, even if it does no work.
How does the
protected
access specifier differ fromprivate
?
base class members:
private:
: base class itself and friends can accessprotected
: base class itself, friends and derived classes can access
Define your own versions of the
Quote
class and theprint_total
function.
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;
- (a) incorrect, derive from itself
- (b) correct.
- (c) incorrect, A derived class is declared like any other class. The declaration contains the class name but does not include its derivation list
Define your own version of the
Bulk_quote
class.
Test your
print_total
function from the exercises in 15.2.1 (p. 595) by passing bothQuote
andBulk_quote
objects o that function.
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.
Define static type and dynamic type.
-
static type: Type with which a variable is defined or that an expression yields. Static type is known at compile time.
-
dynamic type: Type of an object at run time. The dynamic type of an object to which a reference refers or to which a pointer points may differ from the static type of the reference or pointer.
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 pointer or reference to a base-class type can refer to an to object of derived type. In such cases the static type is reference (or pointer) to base, but the dynamic type is reference (or pointer) to derived.
Anything like this can be an example.
Recalling the discussion from 8.1 (p. 311), explain how the program on page 317 that passed an
ifstream
to theSales_data
read
function works.
the function takes a istream
from which ifstream
is derived. Hence the ifstream
object "is a" istream
,which is why it works.
Add a virtual
debug
function to yourQuote
class hierarchy that displays the data members of the respective classes.
Quote | Bulk_quote | Limit_quote
Is it ever useful to declare a member function as both
override
andfinal
? Why or why not?
Sure. override
means overriding the same name virtual function in base class. final
means preventing any overriding this virtual function by any derived classes that are more lower at the hierarchy. (@Mooophy)
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?
The print
in derived::print
wanted to call the print
from the base class. However, the class scope base::
was omitted. As a result, it will cause an infinite recursion.
Fixed:
void print(ostream &os) { base::print(os); os << " " << i; }
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()
(b) dobj.print(); // derived::print()
(c) bp1->name(); // base::name()
(d) bp2->name(); // base::name()
(e) br1.print(); // base::print()
(f) br2.print(); // derived::print()
e and f are called at run time.
Define your own versions of
Disc_quote
andBulk_quote
.
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
.
Try to define an object of type Disc_quote and see what errors you get from the compiler.
note: because the following virtual functions are pure within 'ch15::EX15::Disc_quote':
virtual double net_price(size_t) const = 0;
^
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:
Base *p = &d1; // d1 has type Pub_Derv -- legal
p = &d2; // d2 has type Priv_Derv -- illegal
p = &d3; // d3 has type Prot_Derv -- illegal
p = &dd1; // dd1 has type Derived_from_Public -- legal
p = &dd2; // dd2 has type Derived_from_Private -- illegal
p = &dd3; // dd3 has type Derived_from_Protected -- illegal
Assuming
D
inherits fromB
:User code may use the derived-to-base conversion only if
D
inherits publicly fromB
. User code may not use the conversion ifD
inherits fromB
using eitherprotected
orprivate
.
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.
Assuming
D
inherits fromB
:Member functions and friends of
D
can use the conversion toB
regardless of howD
inherits fromB
. The derived-to-base conversion to a direct base class is always accessible to members and friends of a derived class.
Hence, the 3 below are all legal:
Pub_Derv d1; // legal
Priv_Derv d2; // legal
Prot_Derv d3; // legal
Member functions and friends of classes derived from
D
may use the derived-to-base conversion ifD
inherits fromB
using eitherpublic
orprotected
. Such code may not use the conversion ifD
inherits privately fromB
.
Hence:
Derived_from_Public dd1; // legal
Derived_from_Private dd2; // illegal
Derived_from_Protected dd3; // legal
Write code to test your answers to the previous two exercises.
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)
(b): code
For the class you chose in the previous exercise, identify some of the likely virtual functions as well as public and protected members.
such as shape_name()
, resize_by_percentage(float pct)
, area()
, volume()
, etc.
Assuming class
D1
on page 620 had intended to override its inheritedfcn
function, how would you fix that class? Assuming you fixed the class so thatfcn
matched the definition inBase
, how would the calls in that section be resolved?
What kinds of classes need a virtual destructor? What operations must a virtual destructor perform?
Generally, a base class should define a virtual destructor.
The destructor needs to be virtual to allow objects in the inheritance hierarchy to be dynamically allocated.
Why did we define a default constructor for
Disc_quote
? What effect, if any, would removing that constructor have on the behavior ofBulk_quote
?
#include <string>
using std::string;
class Quote {
public:
Quote() = default;
Quote(string const& b, double p) : bookNo(b), price(p) { }
string isbn() const { return bookNo; }
virtual double net_price(size_t n) const { return n * price; }
virtual ~Quote() = default;
private:
string bookNo;
protected:
double price = 0.0;
};
class Disc_quote : public Quote {
public:
//Disc_quote() = default;
Disc_quote(string const& b, double p, size_t q, double d) : Quote(b, p), quantity(q), discount(d){ }
virtual double net_price(size_t) const = 0;
protected:
size_t quantity = 0;
double discount = 0.0;
};
class Bulk_quote : public Disc_quote {
public:
Bulk_quote() = default;
Bulk_quote(string const& book, double price, size_t qty, double disc) : Disc_quote(book, price, qty, disc) { }
virtual double net_price(std::size_t cnt) const override {
if (cnt >= quantity) return cnt * (1 - discount) * price;
else return cnt * price;
}
};
int main()
{
Bulk_quote b_quote;
}
Without it, when building the upper codes, the compiler would conplain that:
error: use of deleted function 'Bulk_quote::Bulk_quote()'
Bulk_quote b_quote;
^
'Bulk_quote::Bulk_quote()' is implicitly deleted because the default definition would be ill-formed:
Bulk_quote() = default;
^
The reason is that a constructor taking 4 parameters has been defined, which prevented the compiler generate synthesized version default constructor. As a result, the default constructor of any class derived from it has been defined as deleted. Thus the default constructor must be defined explicitly so that the derived classes can call it when executing its default constructor.
Define the
Quote
andBulk_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.
Redefine your
Bulk_quote
class to inherit its constructors.
Define a
vector
to holdQuote
objects but putBulk_quote
objects into thatvector
. Compute the totalnet_price
of all the elements in thevector
.
#include <vector>
#include <numeric>
#include "ex15_27_Bulk_quote.h"
int main()
{
std::vector<Quote> vecQuote;
Bulk_quote bulk0("0-201-82470-1", 50, 5, 0.2);
Bulk_quote bulk1("0-201-82470-1", 50, 3, 0.5);
// total price should be:
std::cout << "bulk_quote's total: " << bulk0.net_price(5) + bulk1.net_price(5) << std::endl;
vecQuote.push_back(bulk0);
vecQuote.push_back(bulk1);
double total = std::accumulate(vecQuote.cbegin(), vecQuote.cend(),0.0, [](double ret, const Quote &obj){
return ret += obj.net_price(5);
});
// total price in the vector.
std::cout << "total in the vector: " << total << std::endl;
}
Output:
bulk_quote's total: 325
total in the vector: 500
Repeat your program, but this time store
shared_ptrs
to objects of typeQuote
. 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.
#include <vector>
#include <numeric>
#include <memory>
#include "ex15_27_Bulk_quote.h"
int main()
{
std::vector<std::shared_ptr<Quote>> vecQuote;
std::shared_ptr<Bulk_quote> spBulk0 = std::make_shared<Bulk_quote>("0-201-82470-1", 50, 5, 0.2);
std::shared_ptr<Bulk_quote> spBulk1 = std::make_shared<Bulk_quote>("0-201-82470-1", 50, 3, 0.5);
// total price should be:
std::cout << "bulk_quote's total: " << spBulk0->net_price(5) + spBulk1->net_price(5) << std::endl;
vecQuote.push_back(spBulk0);
vecQuote.push_back(spBulk1);
double total = std::accumulate(vecQuote.cbegin(), vecQuote.cend(),0.0, [](double ret, std::shared_ptr<Quote> sp){
return ret += sp->net_price(5);
});
// total price in the vector.
std::cout << "total in the vector: " << total << std::endl;
}
Output:
bulk_quote's total: 325
total in the vector: 325
Because derived objects are "sliced down" when assigned to a base-type object. Thus, when put Bulk_quote
object into the vector
of Quote
, all the object "slice down" to Quote
.
If we want to holds objects related by inheritance, we should define the vector
to hold pointers(preferable smart pointers) to the base class. So, the vector
contains are dynamic type of the objects.
Different from previous program, it can be found that 20% and 50% discount has been applied to the total price calculation.
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.
Given that
s1
,s2
,s3
, ands4
are allstring
s, 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)));
-
(a):
WordQuery
->NotQuery
->AndQuery
->OrQuery
-
(b):
WordQuery
->NotQuery
->AndQuery
->OrQuery
(same as the previous one) -
(c):
WordQuery
->AndQuery
->OrQuery
What happens when an object of type
Query
is copied, moved, assigned, and destroyed?
-
copy: While being copied, the synthesized copy constructor is called. It copies the data member into the new object. Since in this case, the data member is a shared pointer, while copying, the corresponding shared pointer points to the same address and the use count from the both shared pointer becomes 2.
-
move: while being moved, the synthesized move constructor is called. It moves the data member into the new object. In this case, the shared pointer from the newly created object will point to the address to which the original shared pointer pointed. After the move operation, the use count of the shared pointer in the new object is 1, whereas the pointer from the original object becomes
nullptr
. -
copy assignment: The synthesized copy assignment will be called. The outcome of this operation is identical with the copy operation.
-
move assignment: The synthesized move assignment will be called. The rest is the same as the move operation.
-
destroy: The synthesized destructor will be called. It will call the destructor of
shared_ptr
which decrements the use count. If the count becomes zero, the destructor fromshared_ptr
will delete the resources it point to.
What about objects of type
Query_base
?
Managed by the synthesized version. Since Query_base
a abstract class, the object of this type is essentially a sub-object of its derived class.