Язык программирования C++. Пятое издание - Стенли Липпман
Шрифт:
Интервал:
Закладка:
Упражнение 15.31. При условии, что s1, s2, s3 и s4 являются строками укажите, какие объекты создаются в следующих выражениях:
(a) Query(s1) | Query(s2) & ~ Query(s3);
(b) Query(s1) | (Query(s2) & ~ Query(s3));
(c) (Query(s1) & (Query(s2)) | (Query(s3) & Query(s4)));
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.
Оператор вывода класса QueryОператор вывода — хороший пример того, как работает вся система запросов:
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?
15.9.3. Производные классы
Самая интересная часть классов, производных от класса Query_base, в том, как они представляются. Класс WordQuery проще всех. Его задача — хранение искомого слова.
Другие классы работают на одном или двух операндах. У класса NotQuery один операнд, а у классов AndQuery и OrQuery — по два. Операндами в каждом из этих классов могут быть объекты любого из реальных классов, производных от класса Query_base: NotQuery может быть применен к WordQuery, как и AndQuery, OrQuery или NotQuery. Для обеспечения такой гибкости операнды следует хранить как указатели на класс Query_base. Таким образом, можно привязать указатель на любой необходимый реальный класс.
Но вместо того, чтобы хранить указатель на класс Query_base, классы будут сами использовать объект Query. Подобно тому, как пользовательский код упрощается при использовании класса интерфейса, можно упростить код собственного класса, используя тот же класс.
Теперь, когда конструкция этих классов известна, их можно реализовать.
Класс WordQueryКласс WordQuery отвечает за поиск заданной строки. Это единственная операция, которая фактически выполняет запрос для данного объекта класса TextQuery:
class WordQuery: public Query_base {
friend class Query; // Query использует конструктор WordQuery
WordQuery(const std::string &s) : query_word (s) { }
// конкретный класс: WordQuery определяет все унаследованные чистые
// виртуальные функции
QueryResult eval(const TextQuery &t) const
{ return t.query(query_word); }
std::string rep() const { return query_word; }
std::string query_word; // искомое слово
};
Подобно классу Query_base, у класса WordQuery нет открытых членов; он должен сделать класс Query дружественным, чтобы позволить ему получать доступ к конструктору WordQuery().
Каждый из конкретных классов запроса должен определить унаследованные чистые виртуальные функции eval() и rep(). Обе функции определены в теле класса WordQuery: функция eval() вызывает функцию-член query() своего параметра типа TextQuery, который фактически осуществляет поиск в файле; функция rep() возвращает строку, которую данный объект класса WordQuery представляет (т.е. query_word).
Определив класс WordQuery, можно определить конструктор Query(), получающий строку:
inline
Query::Query(const std::string &s): q(new WordQuery(s)) { }
Этот конструктор резервирует объект класса WordQuery и инициализирует его указатель-член так, чтобы он указывал на этот недавно созданный объект.
Класс NotQuery и оператор ~Оператор ~ подразумевает создание объекта класса NotQuery, содержащего инверсный запрос: