Для понимания наследования в языке С++ крайне важно знать, как распознаются вызовы функций. Процесс распознавания вызова p->mem()
(или obj.mem()
) проходит в четыре этапа.
• Сначала определяется статический тип объекта p
(или obj
). Поскольку это вызов члена класса, тип будет классом.
• Поиск имени mem
осуществляется в классе, который соответствует статическому типу объекта p
(или obj
). Если функция mem()
не найдена, поиск продолжается в прямом базовом классе и далее по цепи классов, пока имя mem
не будет найдено или пока не будет осмотрен последний класс. Если функция mem()
не будет найдена ни в самом классе, ни в его базовых классах, вызов откомпилирован не будет.
• Как только имя mem
будет найдено, осуществляется обычная проверка соответствия типов (см. раздел 6.1), гарантирующая допустимость найденного определения для данного вызова.
• Если вызов допустим, компилятор создает код, зависящий от того, является ли вызываемая функция виртуальной или нет:
- Если функция mem()
виртуальная и вызов осуществляется через ссылку или указатель, то компилятор создает код, который во время выполнения определяет на основании динамического типа объекта выполняемую версию функции.
- В противном случае, если функция не является виртуальной или если вызов осуществляется для объекта (а не ссылки или указателя), то компилятор создает код обычного вызова функции.
Как уже упоминалось, функции, объявленные во внутренней области видимости, не перегружают функции, объявленные во внешней области видимости (см. раздел 6.4.1). В результате функции, определенные в производном классе, не перегружают функции-члены, определенные в его базовом классе (классах). Подобно любой другой области видимости, если имя члена производного класса (т.е. определенное во внутренней области видимости) совпадает с именем члена базового класса (т.е. именем во внешней области видимости), то в рамках производного класса имя, определенное в производном классе, скрывает имя в базовом классе. Имя функции-члена базового класса скрывается, даже если у функций будут разные списки параметров:
struct Base {
int memfcn();
};
struct Derived : Base {
int memfcn(int); //
};
Derived d; Base b;
b.memfcn(); //
d.memfcn(10); //
d.memfcn(); //
d.Base::memfcn(); //
Объявление функции memfcn()
в классе Derived
скрывает объявление функции memfcn()
в классе Base
. Не удивительно, что первый вызов через объект b
класса Base
вызывает версию в базовом классе. Точно так же второй вызов (через объект d
) вызывает версию класса Derived
. Удивительно то, что третий вызов, d.memfcn()
, некорректен.
Чтобы распознать этот вызов, компилятор ищет имя memfcn
в классе Derived
. Этот класс определяет член по имени memfcn
, и поиск на этом останавливается. Как только имя будет найдено, компилятор далее не ищет. Версия функции memfcn()
в классе Derived
ожидает аргумент типа int
. Поскольку данный вызов такого аргумента не предоставляет, вызов ошибочен.