Путаница в отношении сокрытия имен и виртуальных функций

Ссылаясь на другой вопрос

Рассмотрим код:

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 содержит указатель на virtual void gogo(int a), который должен был быть скрыт.

Меня смущает то, что сокрытие имени приводит к тому, что случай 1) является незаконным, а затем как вызов в 2) разрешается во время выполнения

а) Содержит ли vtable of Derived указатель на gogo(int).

б) Если а) не верно, разрешение вызовов для виртуальных функций переходит к виртуальной таблице базового класса.


person hiteshg    schedule 16.04.2012    source источник
comment
@AndersK Функция Base::gogo(int) действительно скрыта Derived::gogo(int*). Но оператор using Base::gogo; в классе Derived решил бы эту конкретную проблему.   -  person Michael Wild    schedule 16.04.2012
comment
@MichaelWild да, я увидел свою ошибку.   -  person AndersK    schedule 16.04.2012


Ответы (4)


Вы путаете вызовы виртуальных функций и разрешение перегрузки.

Все производные классы имеют виртуальные таблицы, содержащие все виртуальные функции из базового класса и любые дополнительные собственные виртуальные функции. Это используется для разрешения вызовов во время runtime, как в вашем случае 2).

В случае 1) вы получаете сообщение об ошибке из-за разрешения перегрузки во время времени компиляции. Из-за сокрытия имени класс Derived имеет только одну вызываемую функцию. Ваш единственный выбор — вызвать эту функцию с int*.

person Bo Persson    schedule 16.04.2012
comment
Нет ошибки нет. Поскольку обе перегрузки gogo() были объявлены в Base, переопределение одной не скроет другую. - person Stefan Majewsky; 16.04.2012
comment
это объясняет описанное выше поведение. Стандарт С++ (или любая книга/документ) описывает одно и то же. Я имею в виду, можно ли получить цитату. - person Aman Aggarwal; 16.04.2012
comment
@fizzbuzz — имя, объявленное во внутренней области, всегда скрывает имя во внешней области. В этом случае производный класс является внутренней областью, а базовый класс — внешней областью. - person Bo Persson; 16.04.2012
comment
@Bo Все производные классы имеют виртуальные таблицы, содержащие все виртуальные функции из базового класса и любые дополнительные собственные виртуальные функции. Есть ссылка на это? - person hiteshg; 16.04.2012
comment
@hitesg - Нет, в стандарте вообще ничего нет о vtables. Это всего лишь вариант реализации (который на данный момент используют все известные компиляторы). - person Bo Persson; 16.04.2012
comment
@hitesg — попробуйте это для объяснения http://en.wikipedia.org/wiki/Virtual_table#Implementation - person Bo Persson; 16.04.2012

Вы хотите переопределить перегруженную функцию, но правила скрытия так не работают.

«Правило скрытия гласит, что объект во внутренней области скрывает вещи с тем же именем во внешней области».

Обратите внимание, не имеет значения, что у него другая подпись, т.е. gogo(int* a) скроет все gogo(whatever) функции из Базы. Переопределение происходит только тогда, когда ваши функции имеют одинаковое имя, одинаковые подписи и виртуальные (таким образом, будут переопределены только gogo(int* a)).

В книге C++FAQ предлагается использовать «невиртуальные перегрузки, которые вызывают неперегруженные виртуальные машины». (глава 29.05). В основном вы создаете не виртуальные перегруженные функции в базовом классе:

gogo(int a) and gogo(int* a)

который будет вызывать виртуальные функции соответственно:

виртуальный gogo_i(int a) и виртуальный gogo_pi(int* a)

И переопределите эти виртуалы в производном классе.

person Yodo    schedule 16.04.2012

По сути, перегрузка функций происходит только тогда, когда функции с одним и тем же именем определены в одной и той же области. Теперь у базового класса есть своя область, а у производного класса — своя.

Итак, когда вы не переопределяете функцию в производном классе и не вызываете эту функцию, компилятор проверяет область действия производного и обнаруживает, что там не определена такая функция. Затем он проверяет область действия базового класса, обнаруживает функцию и соответственно привязывает вызов функции к этому конкретному определению.

Но когда вы переопределяете функцию с другой сигнатурой, компилятор сопоставляет вызов с этой функцией, видит несоответствие и просто жалуется.

вы можете изменить это поведение, добавив «using Base::gogo;» в определение производного класса. Надеюсь, это объясняет.

person whitetiger    schedule 16.04.2012

Поскольку вы объявили второй obj как Base*, виртуальная таблица предоставляет ему все методы Base. Хотя для виртуальных методов, которые были переопределены Derived, вызываются переопределенные версии, другие методы (или перегруженные методы) по-прежнему являются теми, которые были объявлены в Base.

Однако, если вы объявили указатель как Derived*, виртуальная таблица предоставит ему методы 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.

См. здесь.

person ApprenticeHacker    schedule 16.04.2012
comment
Не будет ли dynamic_cast более подходящим в вашем примере? - person Griwes; 16.04.2012
comment
Насколько я знаю, это не имеет ничего общего с фактическим поиском vtable, а со статической типизацией и сокрытием имени (см. Ответ Бо Перссона). Если тип Derived, применяется эта семантика, если тип Base, применяется другая семантика. - person KillianDS; 16.04.2012