Object-Oriented Programming
Object-Oriented Programming (OOP): A programming style based on data abstraction, inheritance, and dynamic binding.
The three key ideas are:
data abstraction
inheritance
dynamic binding
Inheritance: Defines new classes from existing related classes.
Dynamic Binding: Selects a virtual function at run time according to the actual type of the object.
Polymorphism: Allows code to use objects of related types through a common base-class interface.
15.1 OOP: An Overview
Inheritance models relationships among related types.
For example, a bookstore may have:
Quote
|
+-- Bulk_quote
Quote represents ordinary pricing.
Bulk_quote represents a pricing strategy that applies a discount when enough copies are purchased.
Base Class
Base Class: A class from which other classes inherit.
class Quote
{
public:
std::string isbn() const
{
return book_no;
}
virtual double net_price(
std::size_t count) const
{
return count * price;
}
virtual ~Quote() = default;
protected:
double price = 0.0;
private:
std::string book_no;
};
Derived Class
Derived Class: A class that inherits members from a base class and can add or override behavior.
class Bulk_quote
: public Quote
{
public:
double net_price(
std::size_t count) const override;
private:
std::size_t min_qty = 0;
double discount = 0.0;
};
Dynamic Binding
A function can accept a reference to the base class:
double print_total(
std::ostream& output,
const Quote& item,
std::size_t count)
{
double result =
item.net_price(count);
output
<< item.isbn()
<< ": "
<< result
<< '\n';
return result;
}
Now both objects can be passed:
Quote regular;
Bulk_quote bulk;
print_total(
std::cout,
regular,
10
);
print_total(
std::cout,
bulk,
10
);
Because net_price() is virtual, the function called depends on the actual object.
Quote object
-> Quote::net_price()
Bulk_quote object
-> Bulk_quote::net_price()
In C++, dynamic binding occurs when a virtual function is called through a pointer or reference to a base class.
15.2 Defining Base and Derived Classes
Inheritance relationships are declared in a derivation list.
class Bulk_quote
: public Quote
{
// ...
};
The access specifier controls how the inheritance relationship is exposed.
15.2.1 Defining a Base Class
A base class usually defines:
- common data
- common operations
- virtual operations that derived classes may override
Example:
class Quote
{
public:
Quote() = default;
Quote(
const std::string& book,
double sales_price)
: book_no(book),
price(sales_price)
{
}
std::string isbn() const
{
return book_no;
}
virtual double net_price(
std::size_t count) const
{
return count * price;
}
virtual ~Quote() = default;
protected:
double price = 0.0;
private:
std::string book_no;
};
Virtual Functions
Virtual Function: A member function whose implementation may be selected dynamically according to the object's dynamic type.
virtual double net_price(
std::size_t) const;
A derived class can override this function.
Base Classes Usually Need a Virtual Destructor
If objects may be deleted through a base-class pointer, the base destructor should be virtual.
virtual ~Quote() = default;
Then:
Quote* pointer =
new Bulk_quote;
delete pointer;
correctly destroys the complete derived object.
protected Members
A base class may use protected for implementation data that derived classes need.
protected:
double price = 0.0;
Derived classes may access price.
Ordinary users of the class may not.
15.2.2 Defining a Derived Class
A derived class names its base class in the derivation list.
class Bulk_quote
: public Quote
{
// ...
};
A derived object contains:
- its derived-class part
- a base-class subobject
Conceptually:
Bulk_quote object
+-------------------+
| Quote subobject |
| book_no |
| price |
+-------------------+
| Bulk_quote part |
| min_qty |
| discount |
+-------------------+
This is a conceptual model; physical layout is implementation-dependent.
Public Inheritance
With public inheritance:
class Bulk_quote
: public Quote
```
a `Bulk_quote` can be used where a `Quote` reference or pointer is expected.
~~~cpp
Bulk_quote bulk;
Quote& reference =
bulk;
Quote* pointer =
&bulk;
This works because every Bulk_quote object contains a Quote base subobject.
Overriding a Virtual Function
double net_price(
std::size_t count) const override;
override tells the compiler that this function is intended to override a virtual function from a base class.
If the signatures do not match, the compiler reports an error.
Use override on derived virtual functions whenever possible.
Derived-Class Constructor
A derived constructor initializes its direct base class first.
Bulk_quote(
const std::string& book,
double sales_price,
std::size_t quantity,
double rate)
: Quote(
book,
sales_price),
min_qty(quantity),
discount(rate)
{
}
Initialization order is conceptually:
base-class subobject
↓
derived-class members
↓
derived constructor body
Derived Classes Cannot Initialize Base Members Directly
A derived constructor initializes the base class through a base-class constructor.
It should not try to initialize the base's members directly.
: Quote(book, sales_price)
The base class remains responsible for initializing its own data.
final Classes
A class can prohibit further inheritance.
class FinalClass final
{
};
This is invalid:
// Error:
class Derived
: public FinalClass
{
};
15.2.3 Conversions and Inheritance
A derived object can be converted to a pointer or reference to its accessible base class.
Bulk_quote bulk;
Quote* pointer =
&bulk;
Quote& reference =
bulk;
This conversion selects the base-class part of the derived object.
Static Type
Static Type: The type known at compile time.
Quote& item = bulk;
The static type of item is:
Quote&
Dynamic Type
Dynamic Type: The actual type of the object represented by a pointer or reference at run time.
If:
Bulk_quote bulk;
Quote& item =
bulk;
then:
static type
Quote&
dynamic type
Bulk_quote
The static and dynamic types can differ only for pointers and references.
For an ordinary object variable:
Quote item;
the static and dynamic type are both Quote.
Derived-to-Base Conversion
Derived-to-base pointer/reference conversion is implicit.
void process(
const Quote& item);
Bulk_quote bulk;
process(bulk);
No Implicit Base-to-Derived Conversion
This is not allowed:
Quote base;
// Error:
// Bulk_quote* pointer = &base;
A plain Quote object does not contain a Bulk_quote part.
Even if a base pointer actually points to a derived object, its static type does not automatically convert back.
Bulk_quote bulk;
Quote* base_pointer =
&bulk;
// Error:
// Bulk_quote* derived_pointer
// = base_pointer;
Run-time checked conversions are covered later with dynamic_cast.
Object Slicing
Copying a derived object into a base object copies only the base-class part.
Bulk_quote bulk;
Quote base =
bulk;
The Bulk_quote-specific part is lost.
This is called object slicing.
After slicing:
base.net_price(10);
calls Quote::net_price() because base is a real Quote object.
15.3 Virtual Functions
Virtual functions implement run-time polymorphism.
The function selected depends on the dynamic type only when the call is made through a reference or pointer.
Dynamic Dispatch
Quote base;
Bulk_quote derived;
Quote* pointer =
&derived;
pointer->net_price(10);
The static type of pointer is:
Quote*
but the dynamic type of the pointed-to object is:
Bulk_quote
Therefore the derived override is called.
No Dynamic Binding for Ordinary Objects
Quote base =
derived;
```
After slicing, `base` is a `Quote`.
Therefore:
~~~cpp
base.net_price(10);
is resolved to:
Quote::net_price()
at compile time.
Dynamic binding requires a pointer or reference.
Virtual Functions Must Be Defined
Ordinary nonvirtual functions need definitions only when they are used.
Virtual functions should have definitions because the compiler may need them for dynamic dispatch even when direct usage is not obvious.
A pure virtual function is the major exception.
override
Use:
void function() override;
to verify that a derived member actually overrides a base virtual.
Example:
struct Base
{
virtual void print(
int) const;
};
struct Derived
: Base
{
void print(
int) const override;
};
If the derived signature is wrong, compilation fails.
final on a Virtual Function
A derived override can prevent further overriding.
struct Derived
: Base
{
void print(
int) const final;
};
A class derived from Derived may not override that function.
Virtual Function Default Arguments
Virtual functions can have default arguments.
However, the default argument is chosen from the static type, not the dynamic type.
Therefore base and derived virtual functions should normally use the same default values.
Calling a Specific Base Version
Dynamic dispatch can be bypassed using the scope operator.
pointer->Quote::net_price(10);
This explicitly calls the Quote implementation regardless of the object's dynamic type.
A derived virtual function commonly uses this technique to call base-class behavior.
Avoid Accidental Infinite Recursion
Inside a derived override:
double Derived::function()
{
return Base::function();
}
The qualification is important.
Calling the same virtual function without qualification may dispatch back to the derived override.
15.4 Abstract Base Classes
Some base classes represent concepts that should not have direct objects.
Such classes can contain pure virtual functions.
Pure Virtual Function
Pure Virtual Function: A virtual function declared with = 0.
virtual double net_price(
std::size_t) const = 0;
Example:
class Disc_quote
: public Quote
{
public:
Disc_quote() = default;
Disc_quote(
const std::string& book,
double sales_price,
std::size_t quantity,
double rate)
: Quote(
book,
sales_price),
quantity(quantity),
discount(rate)
{
}
double net_price(
std::size_t) const = 0;
protected:
std::size_t quantity = 0;
double discount = 0.0;
};
Abstract Base Class
Abstract Base Class: A class that contains or inherits a pure virtual function for which no override has been provided.
Objects of an abstract base class cannot be created directly.
// Error:
// Disc_quote item;
A derived class that overrides all pure virtual functions can be concrete.
class Bulk_quote
: public Disc_quote
{
public:
double net_price(
std::size_t) const override;
};
Then:
Bulk_quote item;
is valid.
Abstract Classes Define Interfaces
An abstract base class is useful for expressing a common interface.
Conceptually:
Disc_quote
|
+-- Bulk_quote
+-- Other_discount
```
`Disc_quote` stores data common to discounted pricing but leaves the exact pricing strategy to concrete derived classes.
---
### Pure Virtual Functions May Have Definitions
A pure virtual function may have an out-of-class definition.
However, the `= 0` declaration still makes the class abstract.
Such a definition must not replace the pure-virtual declaration inside the class body.
---
### Derived Constructors Initialize Direct Bases
If:
~~~text
Quote
|
Disc_quote
|
Bulk_quote
then the Bulk_quote constructor initializes only its direct base:
Bulk_quote(...)
: Disc_quote(...)
{
}
Disc_quote then initializes Quote.
Each class initializes its direct base class.
15.5 Access Control and Inheritance
Inheritance adds another layer of access control.
Important concepts include:
public
protected
private
```
and the access specifier used in the derivation list.
---
### `protected` Members
A protected member is accessible to:
- members and friends of the base class
- members and friends of derived classes
It is not accessible to ordinary users.
~~~cpp
class Base
{
protected:
int value = 0;
};
Derived class:
class Derived
: public Base
{
public:
void set(int new_value)
{
value =
new_value;
}
};
User code cannot do:
Derived object;
// Error:
// object.value = 10;
Protected Access Is through the Derived Object
A derived member cannot freely access a protected member through an arbitrary base-class object.
Protected access is intended for the derived object's base part.
The precise rules preserve encapsulation between unrelated base objects.
Public Inheritance
class Derived
: public Base
```
With public inheritance:
~~~text
Base public
-> Derived public
Base protected
-> Derived protected
The derived-to-base conversion is accessible to ordinary users.
This models an is-a relationship.
Protected Inheritance
class Derived
: protected Base
```
Base public and protected members become protected members of the derived class.
Ordinary users cannot use the derived object as a base object.
---
### Private Inheritance
~~~cpp
class Derived
: private Base
```
Base public and protected members become private members of the derived class.
Classes derived further from `Derived` do not get normal access through this private inheritance relationship.
---
### Inheritance Access Summary
| Inheritance | Base `public` becomes | Base `protected` becomes |
|---|---|---|
| `public` | `public` | `protected` |
| `protected` | `protected` | `protected` |
| `private` | `private` | `private` |
Base private members remain inaccessible directly to derived classes in every case.
---
### Derived-to-Base Conversion Accessibility
The derivation access specifier also controls who may perform the derived-to-base conversion.
For public inheritance:
~~~cpp
Derived object;
Base* pointer =
&object;
ordinary code may convert.
For private inheritance, ordinary user code cannot perform that conversion.
Friendship and Inheritance
Friendship is not inherited.
If FriendClass is a friend of Base, it does not automatically become a friend of Derived.
Likewise, derived classes do not automatically gain friendship granted to their base class.
Changing Access with using
A derived class can change the accessibility of an inherited accessible member.
class Base
{
public:
std::size_t size() const
{
return count;
}
protected:
std::size_t count = 0;
};
class Derived
: private Base
{
public:
using Base::size;
protected:
using Base::count;
};
Although inheritance is private:
Derived object;
object.size();
is allowed because size is re-exposed publicly.
Default Inheritance Access
For:
struct Derived
: Base
```
inheritance is public by default.
For:
~~~cpp
class Derived
: Base
```
inheritance is private by default.
Prefer writing the inheritance access explicitly.
---
## 15.6 Class Scope under Inheritance
A derived class has access to names from its own scope and accessible names inherited from base-class scopes.
Name lookup happens before type checking.
---
### Inherited Scope
Conceptually:
~~~text
Derived scope
↓
Base scope
↓
surrounding scopes
A derived member can use inherited names without qualification.
class Base
{
public:
void print();
};
class Derived
: public Base
{
public:
void use()
{
print();
}
};
Name Lookup Happens at Compile Time
The compiler first searches for a name using the static type of the expression.
Dynamic binding happens only after a virtual function name has been found.
This distinction is crucial:
name lookup
compile time
virtual dispatch
run time
```
---
### Derived Members Hide Base Members
A derived declaration with the same name hides base declarations with that name.
~~~cpp
struct Base
{
void function();
};
struct Derived
: Base
{
void function(int);
};
Then:
Derived object;
// Error:
// object.function();
```
The derived `function(int)` hides the base name `function`, even though the parameter lists differ.
---
### Use Scope Qualification to Reach a Hidden Member
~~~cpp
object.Base::function();
or inside the derived class:
Base::function();
This explicitly selects the base member.
Name Lookup before Type Checking
Suppose:
struct Base
{
void function();
};
struct Derived
: Base
{
void function(int);
};
The compiler finds Derived::function first.
It does not continue to search Base merely because the derived candidate has the wrong parameter list.
This differs from overloading within one scope.
Restoring Base Overloads with using
A derived class can bring base overloads into its scope.
struct Base
{
void function();
void function(int);
};
struct Derived
: Base
{
using Base::function;
void function(
double);
};
Now all three forms are visible.
Virtual Functions and Scope
Overriding depends on matching a virtual function from a base class.
Name hiding and overriding are different ideas.
A derived function can hide a base function even when it does not override it.
Use override to detect accidental signature mismatches.
15.7 Constructors and Copy Control
Inheritance affects object construction, destruction, copying, moving, and assignment.
Each derived object contains a base subobject, so copy-control operations must manage both parts correctly.
15.7.1 Virtual Destructors
A polymorphic base class normally needs a virtual destructor.
class Quote
{
public:
virtual ~Quote() = default;
};
Then:
Quote* pointer =
new Bulk_quote;
delete pointer;
invokes the correct complete destruction sequence.
Why Virtual Destruction Matters
Without a virtual base destructor, deleting a derived object through a base pointer has undefined behavior.
For inheritance hierarchies used polymorphically, make the base destructor virtual.
Virtual Destructor and Move Operations
A user-declared destructor affects automatic move generation.
Therefore a base class with a virtual destructor may not automatically receive synthesized move operations under the usual move-generation rules.
Copy-control design still needs deliberate consideration.
15.7.2 Synthesized Copy Control and Inheritance
Synthesized copy-control members process the base-class subobject as well as derived members.
Conceptually, copying a derived object does:
copy base subobject
↓
copy derived members
If a base copy operation is deleted or inaccessible, the corresponding synthesized derived operation may also be deleted.
Base Copy-Control Members Affect Derived Classes
A derived class depends on its base class being:
- constructible
- copyable when copying is needed
- movable when moving is needed
- destructible
If the base operation is unavailable, the derived operation may also become unavailable.
15.7.3 Derived-Class Copy-Control Members
When a derived class defines its own copy constructor, it must explicitly initialize the base part when necessary.
Derived::Derived(
const Derived& other)
: Base(other),
member(other.member)
{
}
If the base initializer is omitted, the base part is default initialized rather than copied.
Derived Copy Assignment
A derived assignment operator should assign the base-class part explicitly.
Derived&
Derived::operator=(
const Derived& rhs)
{
Base::operator=(rhs);
member =
rhs.member;
return *this;
}
The qualified call assigns the base subobject.
Derived Move Operations
A derived move constructor should move the base subobject and derived members.
Conceptually:
Derived::Derived(
Derived&& other)
: Base(
std::move(other)),
member(
std::move(
other.member))
{
}
Move support should be designed consistently throughout the hierarchy.
Destruction Order
Construction proceeds:
base
↓
derived
```
Destruction proceeds in reverse:
~~~text
derived
↓
base
```
The derived destructor body runs before the base destructor.
---
### 15.7.4 Inherited Constructors
A derived class can inherit constructors from a direct base class with a `using` declaration.
~~~cpp
class Bulk_quote
: public Disc_quote
{
public:
using Disc_quote::
Disc_quote;
double net_price(
std::size_t) const override;
};
This makes appropriate base constructors available as derived constructors.
Inherited Constructor Behavior
An inherited constructor initializes the inherited base part using the selected base constructor.
The remaining derived members are initialized normally.
A derived class may also define additional constructors of its own.
Constructors That Are Not Inherited
The following are not inherited as ordinary constructors:
default constructor
copy constructor
move constructor
```
These are generated or defined according to the normal rules for the derived class.
---
### Inherited Constructor Is Not a User-Defined Default Constructor
A class that only inherits constructors may still receive a synthesized default constructor when normal rules permit.
---
## 15.8 Containers and Inheritance
Containers store objects of one fixed element type.
This creates a problem for inheritance hierarchies.
---
### Object Containers Cause Slicing
Consider:
~~~cpp
std::vector<Quote>
basket;
Then:
basket.push_back(
Quote(
"A",
50.0)
);
basket.push_back(
Bulk_quote(
"B",
50.0,
10,
0.25)
);
The Bulk_quote object is converted to a Quote object.
Its derived part is sliced away.
Therefore:
basket.back()
.net_price(15);
calls Quote::net_price().
Store Pointers Instead of Objects
To preserve polymorphism, store pointers to the base class.
Prefer smart pointers:
std::vector<
std::shared_ptr<Quote>
> basket;
Add different dynamic types:
basket.push_back(
std::make_shared<Quote>(
"A",
50.0)
);
basket.push_back(
std::make_shared<
Bulk_quote
>(
"B",
50.0,
10,
0.25)
);
Polymorphic Call through Smart Pointer
double total =
basket.back()
->net_price(15);
The vector element type is:
shared_ptr<Quote>
```
but the pointed-to object may have dynamic type:
~~~text
Quote
or
Bulk_quote
```
Virtual dispatch is preserved.
---
### Smart Pointer Derived-to-Base Conversion
A:
~~~cpp
std::shared_ptr<
Bulk_quote
>
can be converted to:
std::shared_ptr<
Quote
>
when the underlying derived-to-base conversion is valid.
This makes containers of base-class smart pointers convenient for polymorphic objects.
15.8.1 Writing a Basket Class
A helper class can hide the pointer-management details.
Conceptually:
class Basket
{
public:
void add_item(
const std::shared_ptr<
Quote>& item)
{
items.insert(item);
}
private:
std::multiset<
std::shared_ptr<Quote>,
Compare
> items;
};
The public interface can expose domain operations rather than raw container details.
Virtual clone()
A common pattern is to define a virtual function that copies the dynamic type.
Base:
class Quote
{
public:
virtual Quote* clone() const &
{
return new Quote(*this);
}
virtual Quote* clone() &&
{
return new Quote(
std::move(*this)
);
}
virtual ~Quote() = default;
};
Derived:
class Bulk_quote
: public Quote
{
public:
Bulk_quote* clone() const &
override
{
return new Bulk_quote(
*this
);
}
Bulk_quote* clone() &&
override
{
return new Bulk_quote(
std::move(*this)
);
}
};
The derived override may return a pointer to the derived type because pointer return types support covariant return in this situation.
Why clone() Is Useful
If a Basket receives a Quote&, the static type alone is not enough to copy the complete dynamic object.
A virtual clone() preserves the dynamic type.
Conceptually:
Quote reference
↓ virtual clone
actual dynamic type copied
```
---
## 15.9 Text Queries Revisited
The text-query program from Chapter 12 can be extended using inheritance and dynamic binding.
The new goal is to support expressions such as:
~~~text
fiery
~fiery
fiery & bird
fiery | wind
```
These represent:
- word query
- NOT query
- AND query
- OR query
---
### 15.9.1 An Object-Oriented Solution
The class hierarchy separates the interface used by clients from the classes that implement individual query operations.
Conceptually:
~~~text
Query
|
+--> shared_ptr<Query_base>
|
+-- WordQuery
+-- NotQuery
+-- BinaryQuery
|
+-- AndQuery
+-- OrQuery
Query Operations
The public Query class supports operations such as:
Query word("fiery");
Query not_query =
~word;
Query and_query =
word
& Query("bird");
Query or_query =
word
| Query("wind");
Operator overloading creates query-expression objects.
Interface and Implementation Separation
Query is the user-facing interface.
Query_base and its derived classes form the implementation hierarchy.
This hides:
- dynamic allocation
- inheritance details
- ownership of query nodes
from client code.
15.9.2 The Query_base and Query Classes
Query_base is an abstract base class.
class Query_base
{
friend class Query;
protected:
using line_no =
TextQuery::line_no;
virtual ~Query_base()
= default;
private:
virtual QueryResult eval(
const TextQuery&) const
= 0;
virtual std::string rep() const
= 0;
};
Both important operations are pure virtual.
eval()
eval(): Evaluates a query against a TextQuery object and returns matching lines.
Different query types implement different evaluation rules.
rep()
rep(): Returns a textual representation of the query expression.
Examples:
fiery
~(fiery)
(fiery & bird)
(fiery | wind)
```
---
### The `Query` Interface Class
~~~cpp
class Query
{
friend Query operator~(
const Query&);
friend Query operator|(
const Query&,
const Query&);
friend Query operator&(
const Query&,
const Query&);
public:
Query(
const std::string&);
QueryResult eval(
const TextQuery& text) const
{
return query->eval(text);
}
std::string rep() const
{
return query->rep();
}
private:
Query(
std::shared_ptr<
Query_base
> pointer)
: query(
std::move(pointer))
{
}
std::shared_ptr<
Query_base
> query;
};
The public functions delegate to virtual functions through the stored base pointer.
Handle-Class Pattern
Query is a handle class.
It holds a smart pointer to an object in an inheritance hierarchy.
Conceptually:
Query
|
v
shared_ptr<Query_base>
|
v
dynamic query object
```
This combines:
- value-like public syntax
- polymorphic internal implementation
---
### 15.9.3 The Derived Classes
Concrete query classes implement the individual query operations.
---
### `WordQuery`
A word query stores one word.
~~~cpp
class WordQuery
: public Query_base
{
friend class Query;
explicit WordQuery(
const std::string& word)
: query_word(word)
{
}
QueryResult eval(
const TextQuery& text) const override
{
return text.query(
query_word
);
}
std::string rep() const override
{
return query_word;
}
std::string query_word;
};
NotQuery
A NOT query stores another Query.
class NotQuery
: public Query_base
{
friend Query operator~(
const Query&);
explicit NotQuery(
const Query& query)
: query(query)
{
}
std::string rep() const override
{
return
"~("
+ query.rep()
+ ")";
}
QueryResult eval(
const TextQuery&) const override;
Query query;
};
BinaryQuery
AND and OR queries share common data:
left query
right query
operator symbol
```
This common implementation belongs in an abstract intermediate base class.
~~~cpp
class BinaryQuery
: public Query_base
{
protected:
BinaryQuery(
const Query& left,
const Query& right,
std::string symbol)
: lhs(left),
rhs(right),
op_sym(
std::move(symbol))
{
}
std::string rep() const override
{
return
"("
+ lhs.rep()
+ " "
+ op_sym
+ " "
+ rhs.rep()
+ ")";
}
Query lhs;
Query rhs;
std::string op_sym;
};
BinaryQuery remains abstract because it does not implement eval().
AndQuery
class AndQuery
: public BinaryQuery
{
friend Query operator&(
const Query&,
const Query&);
AndQuery(
const Query& left,
const Query& right)
: BinaryQuery(
left,
right,
"&")
{
}
QueryResult eval(
const TextQuery&) const override;
};
OrQuery
class OrQuery
: public BinaryQuery
{
friend Query operator|(
const Query&,
const Query&);
OrQuery(
const Query& left,
const Query& right)
: BinaryQuery(
left,
right,
"|")
{
}
QueryResult eval(
const TextQuery&) const override;
};
Query Operators
The operators create the appropriate derived query object.
Conceptually:
Query operator~(
const Query& operand)
{
return std::shared_ptr<
Query_base
>(
new NotQuery(
operand)
);
}
Likewise:
&
creates AndQuery
|
creates OrQuery
```
The returned `Query` hides the exact derived type.
---
### 15.9.4 The `eval` Functions
Each query type applies a different set operation to line numbers.
---
### `WordQuery::eval()`
A word query delegates directly to `TextQuery`.
~~~cpp
return text.query(
query_word
);
NotQuery::eval()
A NOT query returns lines that are not in its operand result.
Conceptually:
all file lines
-
operand matching lines
```
---
### `OrQuery::eval()`
An OR query returns the union of its two result sets.
Conceptually:
~~~text
left lines
UNION
right lines
```
Example implementation idea:
~~~cpp
auto result =
std::make_shared<
std::set<line_no>
>(
left.begin(),
left.end()
);
result->insert(
right.begin(),
right.end()
);
A set naturally removes duplicates.
AndQuery::eval()
An AND query returns the intersection of the two line sets.
Conceptually:
left lines
INTERSECTION
right lines
```
The standard algorithm can compute this:
~~~cpp
std::set_intersection(
left.begin(),
left.end(),
right.begin(),
right.end(),
std::inserter(
*result,
result->begin())
);
OOP Structure of the Query Program
The final design combines concepts from several chapters:
inheritance
virtual functions
abstract base classes
operator overloading
shared_ptr
set
generic algorithms
```
The user writes expressions through `Query`.
Dynamic binding chooses the implementation hidden behind each query node.
---
## Essential Study Checklist
1. OOP in C++ combines data abstraction, inheritance, and dynamic binding.
2. A base class defines common interface and implementation for related types.
3. A derived class contains a base-class subobject plus its own members.
4. Public inheritance allows a derived object to be used through a base pointer or reference.
5. A virtual function can be overridden by derived classes.
6. Use `override` to verify that a derived function truly overrides a base virtual.
7. Dynamic binding occurs only for virtual calls through pointers or references.
8. The static type is known at compile time; the dynamic type is the actual run-time object type.
9. Derived-to-base conversion is implicit for accessible base pointers and references.
10. There is no implicit base-to-derived conversion.
11. Copying a derived object into a base object slices away the derived part.
12. `final` can prevent further class derivation or further overriding of a virtual function.
13. A pure virtual function is declared with `= 0`.
14. A class with an unimplemented pure virtual function is abstract and cannot be instantiated directly.
15. `protected` members are available to derived classes but hidden from ordinary users.
16. Public, protected, and private inheritance control the accessibility of inherited members and derived-to-base conversions.
17. A derived declaration can hide all base functions with the same name; `using Base::name` can restore base overloads.
18. Polymorphic base classes normally need virtual destructors.
19. Derived copy-control members must correctly handle the base-class subobject.
20. Constructors can be inherited with `using Base::Base`, but default, copy, and move constructors follow their normal synthesis rules.
21. Containers should store smart pointers to base objects rather than base objects themselves when polymorphism must be preserved.
22. `shared_ptr<Derived>` can convert to `shared_ptr<Base>` when the ordinary derived-to-base conversion is valid.
23. A virtual `clone()` can copy an object while preserving its dynamic type.
24. The Chapter 15 query example uses an abstract `Query_base` hierarchy behind a value-like `Query` handle class.
25. Dynamic binding lets `Query` delegate `eval()` and `rep()` to `WordQuery`, `NotQuery`, `AndQuery`, and `OrQuery` without client code knowing their concrete types.