Разрешение перегрузки C ++

В следующем примере, почему я должен явно использовать оператор b->A::DoSomething(), а не просто b->DoSomething()?

Разве разрешение перегрузки компилятора не должно определять, о каком методе я говорю?

Я использую Microsoft VS 2005. (Примечание: использование virtual в этом случае не помогает.)

class A
{
  public:
    int DoSomething() {return 0;};
};

class B : public A
{
  public:
    int DoSomething(int x) {return 1;};
};

int main()
{
  B* b = new B();
  b->A::DoSomething();    //Why this?
  //b->DoSomething();    //Why not this? (Gives compiler error.)
  delete b;
  return 0;
}

person Abe    schedule 16.09.2008    source источник
comment
Я пытаюсь вызвать DoSomething () в классе A, используя указатель на класс B.   -  person Abe    schedule 16.09.2008


Ответы (9)


Две «перегрузки» не относятся к одной области. По умолчанию компилятор рассматривает только наименьшую возможную область имени, пока не найдет совпадение имени. Сопоставление аргументов выполняется впоследствии. В вашем случае это означает, что компилятор видит B::DoSomething. Затем он пытается сопоставить список аргументов, что терпит неудачу.

Одним из решений было бы перетащить перегрузку из A в область B:

class B : public A {
public:
    using A::DoSomething;
    // …
}
person Konrad Rudolph    schedule 16.09.2008
comment
Фактически, если они не находятся в одной области, они также не называются перегрузками. - person Chubsdad; 04.11.2010
comment
@Chubsdad: Вот почему я заключил это слово в кавычки. И как только метод помещается в ту же область видимости, он становится перегрузкой. - person Konrad Rudolph; 04.11.2010

Разрешение перегрузки - одна из самых уродливых частей C ++

Обычно компилятор находит совпадение имени «DoSomething (int)» в области B, видит, что параметры не совпадают, и останавливается с ошибкой.

Это можно преодолеть, используя A :: DoSomething в классе B

class A  
{  
  public:  
    int DoSomething() {return 0;}
};  

class B : public A  
{  
  public:  
    using A::DoSomething;
    int DoSomething(int x) {return 1;} 
};   


int main(int argc, char** argv)
{
  B* b = new B();  
  // b->A::DoSomething();    // still works, but...
  b->DoSomething();    // works now too
  delete b;  
  return 0;
}
person Pieter    schedule 16.09.2008

Нет, такое поведение присутствует, чтобы гарантировать, что вы не попадете в ловушку наследования от удаленных базовых классов по ошибке.

Чтобы обойти это, вам нужно сообщить компилятору, какой метод вы хотите вызвать, поместив using A :: DoSomething в класс B.

См. эту статью для быстрого и легкого обзора этого поведения.

person Lehane    schedule 16.09.2008

Наличие метода в производном классе скрывает все методы с тем же именем (независимо от параметров) в базовых классах. Это сделано, чтобы избежать таких проблем:

class A {} ;
class B :public A
{
    void DoSomething(long) {...}
}

B b;
b.DoSomething(1);     // calls B::DoSomething((long)1));

чем позже кто-то меняет класс A:

class A
{
    void DoSomething(int ) {...}
}

теперь вдруг:

B b;
b.DoSomething(1);     // calls A::DoSomething(1);

Другими словами, если это не сработало, несвязанное изменение в классе, который вы не контролируете (A), могло бы незаметно повлиять на то, как работает ваш код.

person James Curran    schedule 16.09.2008
comment
Должен сказать, очень хороший момент. Большинство ответов, которые я видел, говорили о том, почему возникает ошибка и как ее избежать, но ваша точка зрения показывает, почему эта функция важна и полезна в сценарии реального мира !! - person Pravar Jawalekar; 20.11.2017

Это как-то связано с тем, как работает разрешение имен. По сути, мы сначала находим область, из которой происходит имя, а затем собираем все перегрузки для этого имени в этой области. Однако в вашем случае область видимости - это класс B, а в классе B B :: DoSomething скрывает A :: DOSomething:

3.3.7 Скрытие имени [basic.scope.hiding]

... [отрывок] ...

3 В определении функции-члена объявление локального имени скрывает объявление члена класса с тем же именем; см. basic.scope.class. Объявление члена в производном классе (class.de производный) скрывает объявление члена базового класса с тем же именем; см. class.member.lookup.

Из-за сокрытия имени A :: DoSomething даже не рассматривается для разрешения перегрузки.

person Community    schedule 16.09.2008
comment
Наличие актуальной ссылки на спецификацию - это хорошо. - person Sam; 05.01.2012

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

Вы можете сделать функцию базового класса видимой с помощью объявления using:

class B : public A  
{  
  public:  
    int DoSomething(int x) {return 1;};  
    using A::DoSomething;
};  
person Anthony Williams    schedule 16.09.2008

Это не перегрузка! Это СКРЫВАЕТСЯ!

person ugasoft    schedule 16.09.2008

При поиске в дереве наследования функции, которую следует использовать, C ++ использует имя без аргументов, как только он обнаруживает какое-либо определение, он останавливается, а затем проверяет аргументы. В приведенном примере он останавливается в классе B. Чтобы иметь возможность делать то, что вам нужно, класс B должен быть определен следующим образом:

class B : public A  
{  
  public:
    using A::DoSomething;
    int DoSomething(int x) {return 1;};  
}; 
person Jono    schedule 16.09.2008

Функция скрыта функцией с тем же именем в подклассе (но с другой подписью). Вы можете отобразить его, используя оператор using, как в случае использования A :: DoSomething ();

person slicedlime    schedule 16.09.2008