15.9.2. Классы Query_base и Query
Начнем реализацию с определения класса Query_base:
// абстрактный класс, являющийся базовым для конкретных типов запроса;
// все члены закрыты
class Query_base {
friend class Query;
protected:
using line_no = TextQuery::line_no; // используется в функциях eval()
virtual ~Query_base() = default;
private:
// eval() возвращает соответствующий запросу QueryResult
virtual QueryResult eval(const TextQuery&) const = 0;
// rep() строковое представление запроса
virtual std::string rep() const = 0;
};
Обе функции, eval() и rep(), являются чистыми виртуальными, что делает класс Query_base абстрактным базовым (см. раздел 15.4). Поскольку класс Query_base не предназначен для пользователей и непосредственного использования в производных классах, у него нет открытых членов. Класс Query_base будет использоваться только через объекты класса Query. Класс предоставляет дружественные отношения классу Query, поскольку его члены вызывают виртуальные функции класса Query_base.
Защищенный член line_no будет использоваться в функциях eval(). Деструктор также будет защищен, поскольку он используется (неявно) деструкторами в производных классах.
Класс Query
Класс Query предоставляет интерфейс к иерархии наследования Query_base и скрывает ее. Каждый объект класса Query содержит указатель shared_ptr на соответствующий объект класса Query_base. Поскольку класс Query — единственный интерфейс к классам иерархии Query_base, он должен определить собственные версии функций eval() и rep().
Конструктор Query(), получающий строку, создаст новый объект класса WordQuery и свяжет его указатель-член shared_ptr с этим недавно созданным объектом. Операторы &, | и ~ создают объекты AndQuery, OrQuery и NotQuery соответственно. Эти операторы возвращают объект класса Query, связанный с созданным им объектом. Для поддержки этих операторов класс Query нуждается в конструкторе, получающем указатель shared_ptr на класс Query_base и сохраняющем его. Сделаем этот конструктор закрытым, поскольку объекты класса Query_base не предназначены для определения общим пользовательским кодом. Так как этот конструктор является закрытым, операторы следует сделать дружественными.
Исходя из приведенного выше проекта, сам класс Query довольно прост:
// класс интерфейса для взаимодействия с иерархией
// наследования Query_base
class Query {
// эти операторы должны обращаться к указателю shared_ptr
friend Query operator~(const Query &);
friend Query operator|(const Query&, const Query&);
friend Query operator&(const Query&, const Query&);
public:
Query(const std::string&); // создает новый WordQuery
// функции интерфейса: вызывают соответствующий оператор Query_base
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;
};
Начнем с объявления дружественных операторов, создающих объекты класса Query. Эти операторы должны быть друзьями, чтобы использовать закрытый конструктор.
В открытом интерфейсе для класса Query объявляется, но еще не может быть определен получающий строку конструктор. Этот конструктор создает объект класса WordQuery, поэтому невозможно определить этот конструктор, пока не определен сам класс WordQuery.
Два других открытых члена представляют интерфейс для класса Query_base. В каждом случае оператор класса Query использует свой указатель класса Query_base для вызова соответствующей (виртуальный) функции класса Query_base. Фактически вызываемая версия определяется во время выполнения и будет зависеть от типа объекта, на который указывает указатель q.
Оператор вывода — хороший пример того, как работает вся система запросов:
std::ostream &
operator<<(std::ostream &os, const Query &query) {
// Query::rep() осуществляет виртуальный вызов через свой
// указатель Query_base на rep()
return os << query.rep();
}
При выводе объекта класса Query оператор вывода вызывает (открытую) функцию-член rep() класса Query. Эта функция осуществляет виртуальный вызов через свой указатель-член функции-члена rep() объекта, на который указывает данный объект класса Query.
Query andq = Query(sought1) & Query(sought2);
cout << andq << endl;
Таким образом, когда в коде встречается оператор вывода, он вызывает функцию Query::rep() объекта andq. Функция Query::rep() в свою очередь осуществляет виртуальный вызов через свой указатель класса Query_base на версию функции rep() класса Query_base. Поскольку объект andq указывает на объект класса AndQuery, этот вызов выполнит функцию AndQuery::rep().
Упражнения раздела 15.9.2
Упражнение 15.32. Что будет при копировании, перемещении, присвоении и удалении объекта класса Query?
Упражнение 15.33. А объектов класса Query_base?