А если мы произведем от NameQuery класс StringQuery? Он будет поддерживать сокращенную форму запроса AndQuery, и вместо
beautiful && fiery && bird
можно будет написать:
"beautiful fiery bird"
Унаследует ли StringQuery от класса NameQuery дружественные отношения с Query? Нет. Отношение дружественности не наследуется. Производный класс не становится другом класса, который объявил своим другом один из базовых. Если производному классу требуется стать другом одного или более классов, то эти классы должны предоставить ему соответствующие права явно. Например, у класса StringQuery нет никаких специальных прав доступа по отношению к Query. Если расширенный доступ необходим, то Query должен разрешить его явно.
Упражнение 17.6
Даны следующие определения базового и производных классов:
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 );
(b) void Derived::foobar() { _bar = 1024; }
(c) bool Derived::bar( Base *pb )
{ return _foo_bar == pb-_foo_bar; }
17.4. Конструирование базового и производного классов
Напомним, что объект производного класса состоит из одного или более подобъектов, соответствующих базовым классам, и части, относящейся к самому производному. Например, NameQuery состоит из подобъекта Query и объекта-члена string. Для иллюстрации поведения конструктора производного класса введем еще один член встроенного типа:
class NameQuery : public Query {
public:
// ...
protected:
bool _present;
string _name;
};
Если _present установлен в false, то слово _name в тексте отсутствует.
Рассмотрим случай, когда в NameQuery конструктор не определен. Тогда при определении объекта этого класса
NameQuery nq;
по очереди вызывается конструктор по умолчанию Query, а затем конструктор по умолчанию класса string (ассоциированный с объектом _name). Член _present остается неинициализированным, что потенциально может служить источником ошибок. Чтобы инициализировать его, можно так определить конструктор по умолчанию для класса NameQuery:
inline NameQuery::NameQuery() { _present = false; }
Теперь при определении nq вызываются три конструктора по умолчанию: для базового класса Query, для класса string при инициализации члена _name и для класса NameQuery.
А как передать аргумент конструктору базового класса Query? Ответить на этот вопрос можно, рассуждая по аналогии.
Для передачи одного или более аргументов конструктору объекта-члена мы используем список инициализации членов (здесь можно также задать начальные значения членам, не являющимся объектами классов; подробности см. в разделе 14.5):
inline NameQuery::
NameQuery( const string &name )
: _name( name ), _present( false )
{}
Для передачи одного или более аргументов конструктору базового класса также разрешается использовать список инициализации членов. В следующем примере мы передаем конструктору string аргумент name, а конструктору базового класса Query - объект, адресованный указателем ploc:
inline NameQuery::
NameQuery( const string &name,
vectorlocation *ploc )
: _name( name ), Query( *ploc ), _present( true )
{}
Хотя Query помещен в список инициализации вторым, его конструктор всегда вызывается раньше конструктора для _name. Порядок их вызова следующий:
Конструктор базового класса. Если базовых классов несколько, то конструкторы вызываются в порядке их следования в списке базовых классов, а не в порядке появления в списке инициализации. (О множественном наследовании в этой связи мы поговорим в главе 18.)
Конструктор объекта-члена. Если в классе есть несколько таких членов, то конструкторы вызываются в порядке их объявления в классе, а не в порядке появления в списке инициализации (подробнее см. раздел 14.5).
Конструктор производного класса.
Конструктор производного класса должен стремиться передать значение члена базового класса подходящему конструктору того же класса, а не присваивать его напрямую. В противном случае реализации двух классов становятся сильно связанными и тогда изменить или расширить реализацию базового будет затруднительно. (Ответственность разработчика базового класса ограничивается предоставлением подходящего множества конструкторов.)