Путаница в отношении сокрытия имени и виртуальных функций
Ссылаясь на еще один вопрос
Рассмотрим код:
class Base {
public:
virtual void gogo(int a){
printf(" Base :: gogo (int) \n");
};
virtual void gogo(int* a){
printf(" Base :: gogo (int*) \n");
};
};
class Derived : public Base{
public:
virtual void gogo(int* a){
printf(" Derived :: gogo (int*) \n");
};
};
int main(){
// 1)
Derived * obj = new Derived ;
obj->gogo(7); // this is illegal because of name hiding
// 2)
Base* obj = new Derived ;
obj->gogo(7); // this is legal
}
Для случая 2)
Вызов obj->gogo(7)
разрешается во время выполнения.
поскольку obj->gogo(7)
законно Кажется, это означает, что Derived
содержит PTR для virtual void gogo(int a)
который должен был быть скрыт.
Моя путаница заключается в том, что, поскольку скрытие имени приводит к тому, что случай 1) является недопустимым, тогда способ вызова в 2) разрешается во время выполнения
а) Содержит ли vtable из Derived указатель на gogo (int).
б) Если а) не является истиной, переходит ли разрешение вызовов для виртуальных функций в vtable базового класса.
4 ответа
Вы путаете вызовы виртуальных функций и разрешение перегрузки.
Все производные классы имеют таблицы, содержащие все виртуальные функции из базового класса и любые дополнительные собственные виртуальные функции. Это используется для разрешения вызовов во время выполнения, как в вашем случае 2).
В случае 1) вы получаете ошибку от разрешения перегрузки во время компиляции. Из-за сокрытия имени, класс Derived
имеет только одну вызываемую функцию. Ваш единственный выбор - вызвать эту функцию с int*
,
Вы хотите переопределить перегруженную функцию, но правила скрытия не работают так.
"Правило сокрытия гласит, что сущность во внутренней области видимости скрывает вещи с одинаковыми именами во внешней области".
Обратите внимание, что не имеет значения, что он имеет другую подпись, т.е. gogo(int* a)
скроет все gogo(whatever)
функции с базы. Переопределение происходит только тогда, когда ваши функции имеют одинаковые имена, одинаковые подписи и виртуальные (то есть, только gogo(int* a)
будет переопределено).
В книге часто задаваемых вопросов C++ предлагается использовать "не виртуальные перегрузки, которые вызывают не перегруженные виртуалы". (глава 29.05). В основном вы создаете не виртуальные перегруженные функции в базовом классе:
gogo(int a) and gogo(int* a)
которые будут вызывать виртуальные функции соответственно:
виртуальный gogo_i(int * a) и виртуальный gogo_pi(int* a)
И переопределить это виртуалы в производном классе.
По сути, перегрузка функций происходит только тогда, когда функции с одинаковыми именами определены в одной и той же области. Теперь базовый класс имеет свою собственную область, а производный класс - свою.
Таким образом, когда вы не переопределяете функцию в производном классе и не вызываете эту функцию, компилятор проверяет область действия производного, обнаруживает, что там нет такой определенной функции. Затем он проверяет область действия базового класса, обнаруживает функцию и, соответственно, связывает вызов функции с этим конкретным определением.
Но когда вы переопределяете функцию с другой сигнатурой, компилятор сопоставляет вызов с этой функцией, видит несогласованность и просто жалуется.
Вы можете изменить это поведение, добавив "using Base::gogo; " в определение производного класса. Я надеюсь, что это объясняет.
Так как вы объявили второй obj
как Base*
vtable предоставляет все методы Base
, Хотя для виртуальных методов, которые были переопределены Derived
Вызываются переопределенные версии, другие методы (или перегрузки методов) остаются теми, которые были объявлены в Base
,
Однако, если вы объявили указатель как Derived*
Vtable даст ему методы Derived
скрывая те, которые имели одно и то же имя в Base
, Следовательно, obj->gogo(7);
не будет работать. Точно так же это также незаконно:
Base* obj = new Derived();
// legal, since obj is a pointer to Base, it contains the gogo(int) method.
obj->gogo(7);
// illegal, it has been cast into a pointer to Derived. gogo(int) is hidden.
(reinterpret_cast<Derived*>(obj))->gogo(7);
Это законно:
Derived* obj = new Derived ;
obj->Base::gogo(7); // legal.
Смотрите здесь.