Рассмотрим инициализацию указателя на базовый Query адресом объекта производного NameQuery:
Query *pb = new NameQuery( "sprite" );
При вызове виртуальной функции, определенной в базовом классе Query, например:
pb-eval(); // вызывается NameQuery::eval()
вызывается функция из NameQuery. За исключением вызова виртуальной функции, объявленной в Query и переопределенной в NameQuery, другого способа напрямую добраться до членов класса NameQuery через указатель pb не существует:
если в Query и NameQuery объявлены некоторые невиртуальные функции-члены с одинаковым именем, то через pb всегда вызывается экземпляр из Query;
если в Query и NameQuery объявлены одноименные члены, то через pb обращение происходит к члену класса Query;
если в NameQuery имеется виртуальная функция, отсутствующая в Query, скажем suffix(), то попытка вызвать ее через pb приводит к ошибке компиляции:
// ошибка: suffix() - не член класса Query
pb-suffix();
Обращение к члену или невиртуальной функции-члену класса NameQuery через pb тоже вызывает ошибку компиляции:
// ошибка: _name - не член класса Query
pb-_name;
Квалификация имени члена в этом случае не помогает:
// ошибка: у класса Query нет базового класса NameQuery
pb-NameQuery::_name;
В C++ с помощью указателя на базовый класс можно работать только с данными и функциями-членами, включая виртуальные, которые объявлены (или унаследованы) в самом этом классе, независимо от того, какой фактический объект адресуется указателем. Объявление функции-члена виртуальной откладывает решение вопроса о том, какой экземпляр функции вызвать, до выяснения (во время выполнения программы) фактического типа объекта, адресуемого pb.
Такой подход может показаться недостаточно гибким, но у него есть два весомых преимущества:
поиск виртуальной функции-члена во время выполнения никогда не закончится неудачно из-за того, что фактический тип класса не существует. В таком случае программа просто не смогла бы откомпилироваться;
механизм виртуализации можно оптимизировать. Часто вызов такой функции оказывается не дороже, чем косвенный вызов функции по указателю (детально этот вопрос рассмотрен в [LIPPMAN96a]).
В базовом классе Query определен статический член _text_file:
static vectorstring *_text_file;
Создается ли при порождении класса NameQuery второй экземпляр _text_file, уникальный именно для него? Нет. Все объекты производного класса ссылаются на тот же самый, единственный разделяемый статический член. Сколько бы ни было производных классов, существует лишь один экземпляр _text_file. Можно обратиться к нему через объект производного класса с помощью синтаксиса доступа:
nameQueryObject._text_file; // правильно
Наконец, если производный класс хочет получить доступ к закрытым членам своего базового класса напрямую, то он должен быть объявлен другом базового:
class Query { friend class NameQuery; public: // ... };
Теперь объект NameQuery может обращаться не только к закрытым членам своего подобъекта, соответствующего базовому классу, но и к закрытым и защищенным членам любых объектов Query.
А если мы произведем от 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; }