Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих (Стенли Липпман) 3-е хххх.pdf
Скачиваний:
86
Добавлен:
30.05.2015
Размер:
5.92 Mб
Скачать

С++ для начинающих

900

А так выглядит трассировка выполнения запроса 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:

second:

3

 

first: 2

 

 

first: 2

match

second:

8

bird ( 1 ) lines

 

 

display_location

vector:

second:

3

 

first: 2

 

 

first: 2

 

second:

9

untamed ( 1 ) lines match

 

 

display_location

vector:

second: 2

 

 

first: 3

 

 

( 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:

second:

2

 

first: 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 );

С++ для начинающих

901

Если же у нас есть только указатель на абстрактный класс 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.)

С++ для начинающих

902

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

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.

С++ для начинающих

903

При выполнении конструктора базового класса 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;

Почему в инструкции

С++ для начинающих

904

pq->eval();

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

q.eval();

экземпляр из Query?

Упражнение 17.15

(a) Base* Base::copy( Base* );

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

(b)Base* Base::copy( Base* ); Base* Derived::copy( Derived* );

(c)ostream& Base::print( int, ostream&=cout ); Derived* Derived::copy( Vase* );

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

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

Упражнение 17.16

Маловероятно, что наша программа заработает при первом же запуске и в первый раз, когда прогоняется с реальными данными. Средства отладки полезно включать уже на этапе проектирования классов. Реализуйте в нашей иерархии классов Query виртуальную функцию debug(), которая будет отображать члены соответствующих классов. Поддержите управление уровнем детализации двумя способами: с помощью аргумента, передаваемого функции debug(), и с помощью члена класса. (Последнее позволяет включать или отключать выдачу отладочной информации в отдельных объектах.)

Упражнение 17.17

Найдите ошибку в следующей иерархии классов: