Виртуальная функция С++ скрыта

У меня проблема с наследованием С++.

У меня есть иерархия классов:

class A {
public:
   virtual void onFoo() {}
   virtual void onFoo(int i) {}
};

class B : public A {
public:
    virtual void onFoo(int i) {}
};

class C : public B {
};


int main() {
    C* c = new C();
    c->onFoo(); //Compile error - doesn't exist
}

Мой вопрос: почему это не компилируется? Я понимаю, что C должен наследовать обе функции onFoo от A - и на самом деле это компилируется, если вы удаляете переопределение onFoo в B, но g++ дает ошибку, что C не имеет функции onFoo().

Ответы

Ответ 1

Проблема, с которой вы сталкиваетесь, связана с тем, как работает поиск имени на С++. В частности, при разрешении члена компилятор будет искать статический тип объекта, к которому обращается элемент. Если идентификатор найден в этом классе, то поиск завершается и (в случае функций-членов) начинается перегрузка. Если идентификатор не найден, он будет сканировать иерархию, класс за классом, пытаясь найти идентификатор на один уровень за раз.

В вашем конкретном случае у вас есть c->onFoo(); и c имеет тип c. Компилятор не видит объявления onFoo в c, поэтому он продолжает вверх в иерархии. Когда компилятор проверяет B, он видит, что на этом уровне есть объявление void onFoo(int i), поэтому оно останавливает поиск и пытается перегрузить разрешение. В это время разрешение перегрузки выходит из строя из-за несогласованности аргументов.

Тот факт, что объявление void onFoo(int) присутствует на уровне B, приводит к скрытию остальной части перегрузок в любом базовом классе, поскольку это остановит поиск. Обратите внимание, что это проблема с неквалифицированным поиском, функция все еще существует и применима к объекту, но не будет найдена путем регулярного поиска (вы все равно можете назвать ее как c->A::onFoo()).

Что касается того, как справляться со скрытием, самым простым способом является использование объявления using для приведения функций в область видимости:

class B : A {
public:
   using A::onFoo; // All A::onFoo overloads are *considered* here
   void onFoo( int );
};

Эффект объявления using заключается в том, что при поиске класса B при поиске идентификатора onFoo компилятору также рекомендуется учитывать все перегрузки onFoo в базовом классе, позволяя регулярный поиск, чтобы найти A::onFoo().

Ответ 2

Если вы хотите, чтобы члены базового класса перегружали члены производного класса, вы хотите использовать using:

struct A
{
   virtual void onFoo() {}
   virtual void onFoo(int i) {}
};

struct B : A
{
    using A::onFoo;
    virtual void onFoo(int i) {}
};

struct C : B
{
};


int main()
{
    C* c = new C();
    c->onFoo();
}

Ответ 3

Это имя скрывается, в основном только объявленные переопределения существуют в B, а другие перегрузки в скрыты.

Ответ 4

Методы классов A и B должны быть общедоступными. Это, и вам не хватает полуколоней в конце каждого объявления класса.

class A {
public:
   virtual void onFoo() {}
   virtual void onFoo(int i) {}
};

class B : public A {
public:
    virtual void onFoo(int i) {}
};

class C : public B {
};


int main() {
    C* c = new C();
    c->onFoo(); //Compile error - doesn't exist
}

Ответ 5

Вы забыли модификатор public: до методов как в классах A, так и B. Поэтому метод onFoo является закрытым и, следовательно, не виден нигде вне этих классов.

Ответ 6

Я думаю, вы пропустили добавление этого в class B:

struct B : A
{
    using A::onFoo;
    virtual void onFoo(int i) {}
    void onFoo() {} //This line missing in your code.
};

Теперь это скомпилируется!