Функция с тем же именем, но другой подписью в производном классе

92

У меня есть функция с тем же именем, но с другой сигнатурой в базовом и производном классах. Когда я пытаюсь использовать функцию базового класса в другом классе, который наследуется от производного, я получаю сообщение об ошибке. См. Следующий код:

class A
{
    public:
    void foo(string s){};
};

class B : public A
{
    public:
    int foo(int i){};
};

class C : public B
{
    public:
    void bar()
    {
        string s;
        foo(s);
    }
};

Я получаю следующую ошибку от компилятора gcc:

In member function `void C::bar()': no matching function for call to `C::foo(std::string&)' candidates are: int B::foo(int)

Если я удалю int foo(int i){};из класса Bили переименую foo1, все будет нормально.

Что с этим не так?

Игорь Окс
источник
1
Технически дубликат этого вопроса, но у этого есть лучшее название и ответы.
Трубадур

Ответы:

79

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

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

Если вам нужно вызвать базовую функцию, вам нужно будет ограничить вызов с помощью A::foo(s). Обратите внимание, что это также отключит любой механизм виртуальных функций A::foo(string)одновременно.

CB Bailey
источник
13
также прочитайте ответ litdb: вы можете «показать» базовую функцию с помощью предложения «using A :: foo» в B.
xtofl 04
Правда, я просто искал решение, которое можно было бы использовать на месте вызова, рассматривая базовую иерархию как фиксированную.
CB Bailey,
2
На чем основано это утверждение, за которым следует совет: «Обычно считается плохой практикой иметь функции в производных классах, которые имеют то же имя, что и функции в классе bass, которые не предназначены для переопределения функций базового класса как то, что вы видите, обычно является нежелательным поведением. Обычно предпочтительнее давать разным функциям разные имена " . Что, если семантически они делают то же самое? С ++ предоставляет вам решение проблемы, вызванной этим, как объясняет ответ Йоханнеса.
Nawaz
109

Это потому, что поиск имени останавливается, если он находит имя в одной из ваших баз. В других базах дальше не пойдет. Функция в B затеняет функцию в A. Вы должны повторно объявить функцию A в области B, чтобы обе функции были видны из B и C:

class A
{
    public:
    void foo(string s){};
};

class B : public A
{
    public:
    int foo(int i){};
    using A::foo;
};

class C : public B
{
    public:
    void bar()
    {
        string s;
        foo(s);
    }
};

Изменить: настоящее описание, которое дает Стандарт (с 10.2 / 2):

Следующие шаги определяют результат поиска имени в области класса C. Сначала рассматривается каждое объявление имени в классе и в каждом из его подобъектов базового класса. Имя члена f в одном подобъекте B скрывает имя члена f в подобъекте A, если A является подобъектом базового класса B. Любые объявления, которые так скрыты, исключаются из рассмотрения. Каждое из этих объявлений, которые были введены с помощью объявления-использования, считается исходящим от каждого подобъекта C, который относится к типу, содержащему объявление, указанное в объявлении-использовании. 96) Если результирующий набор объявлений не является все из подобъектов одного типа или набор имеет нестатический член и включает элементы из разных подобъектов, возникает двусмысленность и программа плохо сформирована. В противном случае этот набор является результатом поиска.

В другом месте (чуть выше) он должен сказать следующее:

Для id-выражения [ что-то вроде "foo" ] поиск имени начинается в области класса this; для квалифицированного идентификатора [ что-то вроде «A :: foo», A - это вложенный-указатель-имени ] поиск имени начинается в области видимости вложенного-указателя-имени. Поиск имени выполняется до управления доступом (3.4, раздел 11).

([...] поставлено мной). Обратите внимание, это означает, что даже если ваш foo в B является частным, foo в A все равно не будет найден (потому что управление доступом происходит позже).

Йоханнес Шауб - litb
источник
litb, спасибо за ответ. Но когда я пытаюсь скомпилировать ваш код, я получаю: не могу настроить доступ к void A::foo(class basic_string<char,char_traits<char>,allocator<char> >)' in классу B 'из-за локального метода `int B :: foo (int)' с тем же именем. Может быть, это потому, что я использую старую версию gcc
Игорь Окс
1
да, определенно ошибка компилятора. старые компиляторы использовали "A :: foo;" вместо "using A :: foo;" но первый не рекомендуется в C ++.
Йоханнес Шауб - лит