Хотя наша иерархия классов Query представляется вполне приемлемой, она вовсе не является единственно возможной. Например, AndQuery и OrQuery связаны с бинарной операцией, поэтому они в какой-то степени дублируют друг друга. Можно вынести все данные и функции-члены, общие для них, в абстрактный базовый класс BinaryQuery. Поддерево новой иерархии Query изображено на рисунке 17.2:

Query

BinaryQuery

AndQuery OrQuery

Рис. 17.2. Альтернативная иерархия классов

Класс BinaryQuery - это тоже абстрактный базовый класс, следовательно, его фактические объекты в приложении не появляются. Разумной реализации eval() для него предложить нельзя, поэтому чисто виртуальная функция, объявленная в Query, в классе BinaryQuery останется чисто виртуальной. (Подробнее о таких функциях мы поговорим в разделе 17.5.)

Две функции-члена для доступа - lop() и rop(), общие для обоих классов, переносятся выше, в BinaryQuery, и определяются как нестатические встроенные. Аналогично два члена _lop и _rop, объявленные в обоих классах, также переносятся в BinaryQuery и становятся нестатическими и защищенными. Открытые конструкторы обоих производных классов объединяются в один защищенный конструктор BinaryQuery:

class BinaryQuery : public Query {

public:

const Query *lop() { return _lop; }

const Query *rop() { return _rop; }

protected:

BinaryQuery( Query *lop, Query *rop )

: _lop( lop ), _rop( rop )

{}

Query *_lop;

Query *_rop;

};

Складывается впечатление, что теперь оба производных класса должны предоставить лишь подходящие реализации eval():

// увы! эти определения классов некорректны

class OrQuery : public BinaryQuery {

public:

virtual void eval();

};

class AndQuery : public BinaryQuery {

public:

virtual void eval();

};

Однако в том виде, в котором мы их определили, эти классы неполны. При компиляции самих определений ошибок не возникает, но если мы попытаемся определить фактический объект:

// ошибка: отсутствует конструктор класса AndQuery

AndQuery proust( new NameQuery( "marcel" ),

new NameQuery( "proust " ));

то компилятор выдаст сообщение об ошибке: в классе AndQuery нет конструктора, готового принять два аргумента.

Мы предположили, что AndQuery и OrQuery наследуют конструктор BinaryQuery точно так же, как они наследуют функции-члены lop() и rop(). Однако производный класс не наследует конструкторов базового. (Это могло бы привести к ошибкам, связанным с неинициализированными членами производного класса. Представьте, что будет, если в AndQuery добавить пару членов, не являющихся объектами классов: унаследованный конструктор базового класса для инициализации объекта производного AndQuery применять уже нельзя. Однако программист может этого не осознавать. Ошибка проявится не при конструировании объекта AndQuery, а позже, при его использовании. Кстати говоря, перегруженные операторы new и delete наследуются, что иногда приводит к аналогичным проблемам.)

Каждый производный класс должен предоставлять собственный набор конструкторов. В случае классов AndQuery и OrQuery единственная цель конструкторов - обеспечить интерфейс для передачи двух своих операндов конструктору BinaryQuery. Так выглядит исправленная реализация:

// правильно: эти определения классов корректны

class OrQuery : public BinaryQuery {

public:

OrQuery( Query *lop, Query *rop )

: BinaryQuery( lop, rop ) {}

virtual void eval();

};

class AndQuery : public BinaryQuery {

public:

AndQuery( Query *lop, Query *rop )

: BinaryQuery( lop, rop ) {}

virtual void eval();

};

Если мы еще раз взглянем на рис. 17.2, то увидим, что BinaryQuery - непосредственный базовый класс для AndQuery и OrQuery, а Query -для BinaryQuery. Таким образом, Query не является непосредственным базовым классом для AndQuery и OrQuery.

Конструктору производного класса разрешается напрямую вызывать только конструктор своего непосредственного предшественника в иерархии (виртуальное наследование является исключением из этого правила, да и из многих других тоже: см. раздел 18.5). Например, попытка включить конструктор Query в список инициализации членов объекта AndQuery приведет к ошибке.

Перейти на страницу:

Похожие книги