Query_base
and Query
ClassesWe’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:
using line_no = TextQuery::line_no; // used in the eval functions
virtual ~Query_base() = default;
private:
// eval returns the QueryResult that matches this Query
virtual QueryResult eval(const TextQuery&) const = 0;
// rep is a string representation of the query
virtual std::string rep() const = 0;
};
Both eval
and rep
are pure virtual functions, which makes Query_base
an abstract base class (§15.4, p. 610). Because we don’t intend users, or the derived classes, to use Query_base
directly, Query_base
has no public
members. All use of Query_base
will be through Query
objects. We grant friendship to the Query
class, because members of Query
will call the virtuals in Query_base
.
The protected
member, line_no
, will be used inside the eval
functions. Similarly, the destructor is protected
because it is used (implicitly) by the destructors in the derived classes.
Query
ClassThe Query
class provides the interface to (and hides) the Query_base
inheritance hierarchy. Each Query
object will hold a shared_ptr
to a corresponding Query_base
object. Because Query
is the only interface to the Query_base
classes, Query
must define its own versions of eval
and rep
.
The Query
constructor that takes a string
will create a new WordQuery
and bind its shared_ptr
member to that newly created object. The &
, |
, and ~
operators will create AndQuery
, OrQuery
, and NotQuery
objects, respectively. These operators will return a Query
object bound to its newly generated object. To support these operators, Query
needs a constructor that takes a shared_ptr
to a Query_base
and stores its given pointer. We’ll make this constructor private
because we don’t intend general user code to define Query_base
objects. Because this constructor is private
, we’ll need to make the operators friend
s.
Given the preceding design, the Query
class itself is simple:
// interface class to manage the Query_base inheritance hierarchy
class Query {
// these operators need access to the shared_ptr constructor
friend Query operator~(const Query &);
friend Query operator|(const Query&, const Query&);
friend Query operator&(const Query&, const Query&);
public:
Query(const std::string&); // builds a new WordQuery
// interface functions: call the corresponding Query_base operations
QueryResult eval(const TextQuery &t) const
{ return q->eval(t); }
std::string rep() const { return q->rep(); }
private:
Query(std::shared_ptr<Query_base> query): q(query) { }
std::shared_ptr<Query_base> q;
};
We start by naming as friends the operators that create Query
objects. These operators need to be friends in order to use the private
constructor.
In the public
interface for Query
, we declare, but cannot yet define, the constructor that takes a string
. That constructor creates a WordQuery
object, so we cannot define this constructor until we have defined the WordQuery
class.
The other two public
members represent the interface for Query_base
. In each case, the Query
operation uses its Query_base
pointer to call the respective (virtual) Query_base
operation. The actual version that is called is determined at run time and will depend on the type of the object to which q
points.
Query
Output OperatorThe output operator is a good example of how our overall query system works:
std::ostream &
operator<<(std::ostream &os, const Query &query)
{
// Query::rep makes a virtual call through its Query_base pointer to rep()
return os << query.rep();
}
When we print a Query
, the output operator calls the (public) rep
member of class Query
. That function makes a virtual call through its pointer member to the rep
member of the object to which this Query
points. That is, when we write
Query andq = Query(sought1) & Query(sought2);
cout << andq << endl;
the output operator calls Query::rep
on andq
. Query::rep
in turn makes a virtual call through its Query_base
pointer to the Query_base
version of rep
. Because andq
points to an AndQuery
object, that call will run AndQuery::rep
.