An Overview
The key ideas in object-oriented programming aredata 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(run-time bingding), we can use objects of these types while ignoring the details of how they differ
class Quote
{
public:
Quote() = default; //price = 0.0,bookNo = ""
Quote(const std::string& book,const double sales_price)
:bookNo(book),price(sales_price){}
std::string isbn() const{return bookNo;}
virtual double net_price(const std::size_t n) const {return n*price;}
virtual ~Quote() = default; //dynamic binding for the destructor
protected:
double price = 0.0;
private:
std::string bookNo;
};
void print_total(std::ostream& os,const Quote &item,std::size_t n)
{
double ret = item.net_price(n);
os<<"ISBN: "<<item.isbn()//calls Quote::isbn
<<"# sold: "<<n<<" total due: "<<ret<<std::endl;
}
class Bluk_quote : public Quote
{
public:
Bluk_quote() = default;
Bluk_quote(const std::string& book,const double sales_price,const std::size_t& qty,const double& d)
:Quote(book,sales_price),min_qty(qty),discount(d){}
virtual double net_price(std::size_t n) const override
{
if(n>=min_qty)
return n*(1-discount)*price;
else
return n*price;
}
private:
std::size_t min_qty = 0; //minimum purchase for the discount to apply
double discount = 0.0; //fractional discount to display
};
15.2. Defining Base and Derived Classes
note
In C++, dynamic binding happens when a virtual function is called through a reference (or a pointer) to a base class.
note
Base classes ordinarily should define a virtual destructor. Virtual destructors are needed even if they do no work.
note
Member functions that are not declared as virtual are resolved at compile time, not run time.
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
What is a virtual member?
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 from private?
- private member: base class itself and friend can access
- protected members: base class itself, friend and derived classes can access
15.2.2. Defining a 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
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
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
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
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
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
to how inheritance works
Derived-Class Constructors
note
Each class controls how its members are initialized
Bulk_quote(const std::string& book, double p,
std::size_t qty, double disc) :
Quote(book, p), min_qty(qty), discount(disc) { }
// as before
};
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
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 classs.
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
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
The fact that a derived object contains subobjects for its base classes is key
to how inheritance works
to how inheritance works
Using Members of the Base Class from the Derived Class
A derived class may access the public and protected members of its base class
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
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
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&);
};
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
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 { ... };
class Base { /* ... */ } ;
class D1: public Base { /* ... */ };
class D2: public D1 { /* ... */ };
Preventing Inheritance
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