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

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

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

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

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]