0

Переопределение перегруженной функции базового класса в C++

9

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

Например:

class foo
{
  public:
    foo(void);
    ~foo(void);
    virtual void a(int);
    virtual void a(double);
};

class bar : public foo 
{
  public:
    bar(void);
    ~bar(void);
    void a(int);
};

Попытка вызвать функцию a(double) из класса bar приводит к ошибке компиляции, указывая на то, что функция a(double) не найдена:

int main() 
{
  double i = 0.0;
  bar b;
  b.a(i);  // Ошибка компиляции
}

Как я могу решить эту проблему?

3 ответ(ов)

0

В классе bar добавьте

using foo::a;

Это распространенная "цепочка" в C++. Когда в области видимости класса находится совпадение имени, компилятор не ищет дальше по дереву наследования для перегрузок. Указывая директиву using, вы переносите все перегрузки a из foo в область видимости bar. После этого все работает должным образом.

Учтите, что если в существующем коде используется класс foo, его поведение может измениться из-за добавленных перегрузок. Либо новые перегрузки могут привести к неоднозначности, и код не скомпилируется. Об этом упомянуто в ответе Джеймса Хопкинса.

0

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

class bar : public foo 
{
  public:
    bar(void);
    ~bar(void);
    void a(int);
    void a(double d) { foo::a(d); }  // добавьте это
}

Это вызывало достаточное раздражение у разработчиков, и комитет языка добавил возможность using, но некоторые старые привычки трудно искоренить; и завсегдатаи† имеют довольно убедительные аргументы.

Как указывает Джеймс Хопкинс, добавляя using, программист выражает намерение, что производный класс будет без предупреждений добавлять любые будущие переопределения foo::a() в свой список допустимых сигнатур.

Вот пример того, о чем он говорит:

#include <iostream>
class Base {
public:
  virtual void f(double){ std::cout << "Base::Double!" << std::endl; }
  // virtual void f(int) { std::cout << "Base::Int!" << std::endl; } // (1)
  virtual ~Base() {}
};

class Derived : public Base {
public:
  // using Base::f; // (2)
  void f(double) { std::cout << "Derived::Double!" << std::endl; }
};

int main(int, char **) {
  Derived d;
  d.f(21);
  return 0;
}

Вывод будет "Derived::Double!", потому что компилятор преобразует целочисленный аргумент в double. Компилятор g++ 4.0.1 с ключом -Wall не выдаст предупреждения о том, что произошло это преобразование.

Рассмотрим (1) — раскомментируйте строку, чтобы смоделировать будущее изменение в Base, добавляя метод Basef(int). Код скомпилируется снова без предупреждений, даже с включенным -Wall, и вывод останется "DerivedDouble!".

Теперь раскомментируйте (2), чтобы смоделировать решение программиста Derived включить все сигнатуры Basef. Код компилируется (без предупреждений), но вывод теперь "BaseInt!".

† Я не могу найти подходящее английское слово для «тех, кто имеет привычку», а «зависимый» слишком сильно.

0

Это сделано намеренно. Разрешение перегрузки ограничивается единственным контекстом. Это предотвращает некоторые неприятные ситуации, когда валидный код меняет свое значение при добавлении новых функций в базовый класс или в область видимости пространства имен.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь