Выбрать главу

first: 2 second: 9

Requested query: fiery && ( bird || untamed )

( 3 ) like a fiery bird in flight. A beautiful fiery bird, he tells her,

17.5.7. Почти виртуальный оператор new

Если дан указатель на один из конкретных подтипов запроса, то разместить в хипе дубликат объекта несложно:

NotQuery *pnq;

// установить pnq ...

// оператор new вызывает

// копирующий конструктор NotQuery ...

NotQuery *pnq2 = new NotQuery( *pnq );

Если же у нас есть только указатель на абстрактный класс Query, то задача создания дубликата становится куда менее тривиальной:

const Query *pq = pnq-op();

// как получить дубликат pq?

Если бы позволялось объявить виртуальный экземпляр оператора new, то проблема была бы решена, поскольку автоматически вызывался бы нужный экземпляр. К сожалению, это невозможно: new - статическая функция-член, которая применяется к неструктурированной памяти еще до конструирования объекта класса (см. раздел 15.8).

Но хотя оператор new нельзя сделать виртуальным, разрешается создать его суррогат, который будет выделять память из хипа и копировать туда объекты, - clone():

class Query {

public:

virtual Query *clone() = 0;

// ...

};

Вот как он может быть реализован в классе NameQuery:

class NameQuery : public Query {

public:

virtual Query *clone()

// вызывается копирующий конструктор класса NameQuery

{ return new NameQuery( *this ); }

// ...

};

Это работает правильно, если тип целевого указателя Query*:

Query *pq = new NameQuery( "valery" );

Query *pq2 = pq-clone();

Если же его тип равен NameQuery*, нужно привести возвращенный указатель типа Query* назад к типу NameQuery*:

NameQuery *pnq = new NameQuery( "Rilke" );

NameQuery *pnq2 =

static_castNameQuery*( pnq-clone() );

(Причина, по которой необходимо преобразование типа, объясняется в разделе 19.1.1.)

Как правило, тип значения, возвращаемого реализацией виртуальной функции в производном классе, должен совпадать с типом, возвращаемым ее реализацией в базовом. Исключение, о котором мы уже упоминали, призвано поддержать рассмотренную ситуацию. Если виртуальная функция в базовом классе возвращает значение некоторого типа класса (либо указатель или ссылку на тип класса), то ее реализация в производном может возвращать значение, тип которого является производным от этого класса с открытым типом наследования (то же относится к ссылкам и указателям):

class NameQuery : public Query {

public:

virtual NameQuery *clone()

{ return new NameQuery( *this ); }

// ...

};

Теперь pq2 и pnq2 можно инициализировать без явного приведения типов:

// Query *pq = new NameQuery( "Broch" );

Query *pq2 = pq-clone(); // правильно

// NameQuery *pnq = new NameQuery( "Rilke" );

NameQuery *pnq2 = pnq-clone(); // правильно

Так выглядит реализация clone() в классе NotQuery:

class NotQuery : public Query {

public:

virtual NotQuery *clone()

{ return new NotQuery( *this ); }

// ...

};

Реализации в AndQuery и OrQuery аналогичны. Чтобы эти реализации clone() работали правильно, в классах NotQuery, AndQuery и OrQuery должны быть явно определены копирующие конструкторы. (Мы займемся этим в разделе 17.6.)

17.5.8. Виртуальные функции, конструкторы и деструкторы

Как мы видели в разделе 17.4, для объекта производного класса сначала вызывается конструктор базового, а затем производного класса. Например, при таком определении объекта NameQuery

NameQuery poet( "Orlen" );

сначала будет вызван конструктор Query, а потом NameQuery.

При выполнении конструктора базового класса Query часть объекта, соответствующая классу NameQuery, остается неинициализированной. По существу, poet - это еще не объект NameQuery, сконструирован лишь его подобъект.

Что должно происходить, если внутри конструктора базового класса вызывается виртуальная функция, реализации которой существуют как в базовом, так и в производном классах? Какая из них должна быть вызвана? Результат вызова реализации из производного класса в случае, когда необходим доступ к его членам, оказался бы неопределенным. Вероятно, выполнение программы закончилось бы крахом.

Чтобы этого не случилось, в конструкторе базового класса всегда вызывается реализация виртуальной функции, определенная именно в базовом. Иными словами, внутри такого конструктора объект производного класса рассматривается как имеющий тип базового.