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

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

870

class Base { public:

foo( int ); // ...

protected: int _bar;

double _foo_bar;

};

class Derived : public Base { public:

foo( string );

bool bar( Base *pb ); void foobar();

// ...

protected: string _bar;

};

Derived d; d.foo( 1024 );

Исправьте ошибки в каждом из следующих фрагментов кода:

(c) bool Derived::bar( Base *pb )

(b)void Derived::foobar() { _bar = 1024; }

{return _foo_bar == pb->_foo_bar; }

17.4.Конструирование базового и производного классов

Напомним, что объект производного класса состоит из одного или более подобъектов, соответствующих базовым классам, и части, относящейся к самому производному. Например, NameQuery состоит из подобъекта Query и объекта-члена string. Для

иллюстрации поведения конструктора производного класса введем еще один член

class NameQuery : public Query { public:

// ...

protected:

bool _present; string _name;

встроенного типа:

};

Если _present установлен в false, то слово _name в тексте отсутствует.

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

871

Рассмотрим случай, когда в NameQuery конструктор не определен. Тогда при

определении объекта этого класса

NameQuery nq;

по очереди вызывается конструктор по умолчанию Query, а затем конструктор по умолчанию класса string (ассоциированный с объектом _name). Член _present остается неинициализированным, что потенциально может служить источником ошибок. Чтобы инициализировать его, можно так определить конструктор по умолчанию для класса

NameQuery:

inline NameQuery::NameQuery() { _present = false; }

Теперь при определении nq вызываются три конструктора по умолчанию: для базового класса Query, для класса string при инициализации члена _name и для класса

NameQuery.

А как передать аргумент конструктору базового класса Query? Ответить на этот вопрос можно, рассуждая по аналогии.

Для передачи одного или более аргументов конструктору объекта-члена мы используем список инициализации членов (здесь можно также задать начальные значения членам, не

inline NameQuery::

NameQuery( const string &name )

: _name( name ), _present( false )

являющимся объектами классов; подробности см. в разделе 14.5):

{}

Для передачи одного или более аргументов конструктору базового класса также разрешается использовать список инициализации членов. В следующем примере мы передаем конструктору string аргумент name, а конструктору базового класса Query

inline NameQuery::

NameQuery( const string &name, vector<location> *ploc )

: _name( name ), Query( *ploc ), _present( true )

объект, адресованный указателем ploc:

{}

Хотя Query помещен в список инициализации вторым, его конструктор всегда вызывается раньше конструктора для _name. Порядок их вызова следующий:

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

Конструктор объекта-члена. Если в классе есть несколько таких членов, то конструкторы вызываются в порядке их объявления в классе, а не в порядке появления в списке инициализации (подробнее см. раздел 14.5).

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

872

Конструктор производного класса.

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

противном случае реализации двух классов становятся сильно связанными и тогда изменить или расширить реализацию базового будет затруднительно. (Ответственность

разработчика базового класса ограничивается предоставлением подходящего множества конструкторов.)

В оставшейся части этого раздела мы последовательно изучим конструктор базового класса и конструкторы четырех производных от него, а после этого рассмотрим альтернативный дизайн иерархии классов Query, чтобы познакомиться с иерархиями глубиной больше двух. В конце раздела речь пойдет о деструкторах классов.

17.4.1. Конструктор базового класса

class Query { public:

// ...

protected:

set<short> *_solution; vector<location> _loc;

//...

Внашем базовом классе объявлено два нестатических члена: _solution и _loc:

};

Конструктор Query по умолчанию должен явно инициализировать только член _solution. Для инициализации _loc автоматически вызывается конструктор класса vector. Вот реализация нашего конструктора:

inline Query::Query(): _solution( 0 ) {}

ВQuery нам понадобится еще один конструктор, принимающий ссылку на вектор

inline Query::

Query( const vector< locaton > &loc )

:_solution( 0 ), _loc( loc )

позиций:

{}

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

Какой уровень доступа обеспечить для конструкторов? Мы не хотим объявлять их открытыми, так как предполагается, что Query будет существовать в программе только в

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

873

виде подобъекта в составе объектов производных от него классов. Поэтому мы объявим

class Query { public:

// ...

protected:

Query(); // ...

конструктор не открытым, а защищенным:

};

Ко второму конструктору класса Query предъявляются еще более жесткие требования: он не только должен конструировать Query в виде подобъекта производного класса, но этот производный класс должен к тому же быть NameQuery. Можно объявить конструктор закрытым, а NameQuery сделать другом класса Query. (В предыдущем разделе мы говорили, что производный класс может получить доступ только к открытым и защищенным членам базового. Поэтому любая попытка вызвать второй конструктор из

class Query { public:

//...

protected:

Query();

//...

private:

explicit Query( const vector<location>& );

классов AndQuery, OrQuery или NotQuery приведет к ошибке компиляции.)

};

(Необходимость второго конструктора спорна; вероятно, правильнее заполнить _loc в функции eval() класса NameQuery. Однако принятый подход в большей степени отвечает нашей цели проиллюстрировать использование конструктора базового класса.)

17.4.2. Конструктор производного класса

В классе NameQuery также определены два конструктора. Они объявлены открытыми,

class NameQuery : public Query { public:

explicit NameQuery( const string& );

NameQuery( const string&, const vector<location>* );

//...

protected:

//...

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

};

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

874

Конструктор с одним параметром принимает в качестве аргумента строку. Она передается конструктору объекта типа string, который вызывается для инициализации

inline NameQuery::

NameQuery( const string &name )

// Query::Query() вызывается неявно : _name( name )

члена _name. Конструктор по умолчанию базового класса Query вызывается неявно:

{}

Конструктор с двумя параметрами также принимает строку в качестве одного из них. Второй его параметр это указатель на вектор позиций. Он передается закрытому конструктору базового класса Query. (Обратите внимание, что _present нам больше не

inline NameQuery::

NameQuery( const string &name, vector<location> *ploc ) : _name( name ), Query( *ploc )

нужен, и мы исключили его из числа членов NameQuery.)

{}

string title( "Alice" ); NameQuery *pname;

//проверим, встречается ли "Alice" в отображении слов

//если да, получить ассоциированный с ним вектор позиций

if ( vector<location> *ploc = retrieve_location( title )) pname = new NameQuery( title, ploc );

Конструкторы можно использовать так: else pname = new NameQuery( title );

В каждом из классов NotQuery, OrQuery и AndQuery определено по одному

inline NotQuery::

NotQuery( Query *op = 0 ) : _op( op ) {}

inline OrQuery::

OrQuery( Query *lop = 0, Query *rop = 0 ) : _lop( lop ), _rop( rop )

{}

inline AndQuery::

AndQuery( Query *lop = 0, Query *rop = 0 ) : _lop( lop ), _rop( rop )

конструктору, каждый из которых вызывает конструктор базового класса неявно:

{}