Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих.pdf
Скачиваний:
183
Добавлен:
01.05.2014
Размер:
3.97 Mб
Скачать

А так выглядит трассировка выполнения запроса AndQuery, в которой мы выводим векторы позиций обоих операндов и результирующий вектор:

==> fiery && bird

fiery ( 1 ) lines match

display_location

vector:

first: 2

second: 2

first: 2

second: 8

bird ( 1 ) lines

match

display_location

vector:

first: 2

second: 3

first: 2

second: 9

fiery && bird ( 1 ) lines match

display_location

vector:

first: 2

second: 2

first: 2

second: 3

first: 2

second: 8

first: 2

second: 9

Requested query: fiery && bird

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

Приведем трассировку выполнения составного запроса, включающего как И, так и ИЛИ. Показаны векторы позиций каждого операнда, а также результирующий вектор:

==> fiery && ( bird || untamed ) fiery ( 1 ) lines match

display_location

vector:

first: 2

second: 3

first: 2

second: 8

bird ( 1 ) lines

match

display_location

vector:

first: 2

second: 3

first: 2

second: 9

untamed ( 1 ) lines match

display_location

vector:

first: 3

second: 2

( bird || untamed ) ( 2 ) lines match

display_location vector:

second: 3

first: 2

first:

2

second:

9

first:

3

second:

2

fiery && ( bird || untamed ) ( 1 ) lines match

display_location

vector:

first: 2

second: 2

first: 2

second: 3

first: 2

second: 8

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 нельзя сделать виртуальным, разрешается создать его суррогат,

class Query { public:

virtual Query *clone() = 0;

// ...

который будет выделять память из хипа и копировать туда объекты, – clone():

};

class NameQuery : public Query { public:

virtual Query *clone()

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

{ return new NameQuery( *this ); }

//...

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

};

Query *pq = new

NameQuery( "valery" );

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

Query *pq2 = pq->clone();

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

NameQuery *pnq = new

NameQuery( "Rilke" );

NameQuery *pnq2 =

Query* назад к типу NameQuery*: static_cast<NameQuery*>( pnq->clone() );

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

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

class NameQuery : public Query { public:

virtual NameQuery *clone() { return new

NameQuery( *this ); }

// ...

ссылкам и указателям):

};

// Query *pq

= new NameQuery( "Broch" );

Query *pq2 =

pq->clone(); // правильно

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

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

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

class NotQuery : public Query { public:

virtual NotQuery *clone() { return new

NotQuery( *this ); }

// ...

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

};

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

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

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

NameQuery poet( "Orlen" );

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

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

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

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

То же самое справедливо и внутри деструктора базового класса, вызываемого для объекта производного. И в этом случае часть объекта, относящаяся к производному классу, не определена: не потому, что еще не сконструирована, а потому, что уже уничтожена.

Упражнение 17.12

Внутри объекта NameQuery естественное внутреннее представление вектора позиций – это указатель, который инициализируется указателем, хранящимся в отображении слов. Оно же является и наиболее эффективным, так как нам нужно скопировать лишь один адрес, а не каждую пару координат. Классы AndQuery, OrQuery и NotQuery должны конструировать собственные векторы позиций на основе вычисления своих операндов. Когда время жизни объекта любого из этих классов завершается, ассоциированный с ним вектор позиций необходимо удалить. Когда же заканчивается время жизни объекта NameQuery, вектор позиций удалять не следует. Как сделать так, чтобы вектор позиций был представлен указателем в базовом классе Query и при этом его экземпляры для объектов AndQuery, OrQuery и NotQuery удалялись, а для объектов NameQuery – нет? (Заметим, что нам не разрешается добавить в класс Query признак, показывающий, нужно ли применять оператор delete к вектору позиций!)

Упражнение 17.13 Что неправильно в приведенном определении класса:

class AbstractObject { public:

~AbstractObject(); virtual void doit() =

0;

// ...

};

Упражнение 17.14

NameQuery

nq( "Sneezy" );

Query q( nq );

Даны такие определения:

Query *pq = &nq;

Почему в инструкции pq->eval();

вызывается экземпляр eval() из класса NameQuery, а в инструкции

q.eval();

экземпляр из Query? Упражнение 17.15

(a) Base*

Base::copy( Base* );

Какие из повторных объявлений виртуальных функций в классе Derived неправильны:

Base* Derived::copy( Derived* );

(b) Base*

Base::copy( Base* );

Derived* Derived::copy( Vase* );

(c) ostream& Base::print( int, ostream&=cout );

ostream& Derived::print( int, ostream& );

(d) void Base::eval() const;

void Derived::eval();