2010-06-23 3 views
7

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

class Abase{}; 
class A1:public Abase{}; 
class A2:public A1{}; 
//etc 

class Bbase{ 
public: 
    virtual void f(Abase* a); 
    virtual void f(A1* a); 
    virtual void f(A2* a); 
}; 

class B1:public Bbase{ 
public: 
    void f(A1* a); 
}; 

class B2:public Bbase{ 
public: 
    void f(A2* a); 
}; 

int main(){ 
    A1* a1=new A1(); 
    A2* a2=new A2(); 
    Bbase* b1=new B1(); 
    Bbase* b2=new B2(); 
    b1->f(a1); // calls B1::f(A1*), ok 
    b2->f(a2); // calls B2::f(A2*), ok 
    b2->f(a1); // calls Bbase::f(A1*), ok 
    b1->f(a2); // calls Bbase::f(A2*), no- want B1::f(A1*)! 
} 

Мне интересно знать, почему C++ выбирает разрешить вызов функции на последней строке Приведение к базовому типу в this указатель объекта к основанию класс, а не повышать значение аргумента f()? Есть ли способ, которым я могу получить поведение, которое я хочу?

ответ

10

Выбор какой версии f для звонка осуществляется путем определения типа параметра . Тип времени выполнения не учитывается при этом разрешении имен. Так как b1 имеет тип Bbase*, считается членом всех Bbase; тот, который принимает A2*, является лучшим совпадением, так что это тот, который вызывается.

+0

Спасибо - как я понимаю, дело в том, что разрешение виртуального f() для вызова происходит во время компиляции, основываясь на аргументе, предоставленном f(). Итак, на последней строке компилятор уже решил, что будет вызван f (A2 *). Вызываемая тогда версия f (A2 *) зависит от того, на какой указатель времени выполнения указывается. Здесь, поскольку класс B1 не переопределяет f (A2 *), вызывается версия базового класса. – stw

+0

@stw, совершенно правильный. –

1
b1->f(static_cast<A1*>(a2)); 

Это должно заставить компилятор использовать метод перегрузки с параметром типа A1.

+0

dynamic_cast является более подходящим, я думаю. – Jason

+2

@Jason: «static_cast» для базового класса в порядке. –

+2

Я думаю, что статический актер в порядке, так как мы: а) знаем, что актерский состав верен, и б) он взвинчен, что безопасно. –

0

Это называется скрытым именем. Каждый f, который вы объявляете в одном производном классе, теняет все возможные f в любом из его базовых классов.

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

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

+0

Нет, проблема здесь в другом. Если бы вы вызывали указатель на более производный класс, который скрывает метод (ы) из базового класса, он будет скрывать имена. –

+0

oooo Вы правы, я читал слишком быстро. –

2

«... решает разрешить вызов функции в последней строке, увеличивая этот указатель объекта до базового класса ...». О чем ты говоришь? Во всех ваших вызовах тип указателя объекта равен Bbase *, а функции, которые вызовы разрешают принадлежать либо Bbase, либо его потомкам. Компилятор никогда не выполняет никаких повышений, чтобы разрешить ваши звонки. Фактически, для первых двух вызовов требуется downcasting, чтобы вызвать правильный переулок, так как overrider принадлежит классу, расположенному дальше в иерархии. Что касается последних двух вызовов - они отправляются в класс Bbase с помощью указателя Bbase *. Типы соответствуют точно, никакого литья не происходит.

Что касается разрешения перегрузки ... Разрешение перегрузки - это процесс времени компиляции, основанный на статических типах аргументов и рядах возможных преобразований. Вы указали аргумент A2 *. Кандидат f(A2 *) соответствовал вашему аргументу точно. Кандидату f(A1 *) требуется дополнительная конверсия от A2 * до A1 *. Кандидат, который точно соответствует, считается лучшим, поэтому он выигрывает разрешение перегрузки. Просто.

+0

Извините, я использовал термин «восход» неверным образом. Я имел в виду, почему объект рассматривается как BBase здесь. Ответ (как вы говорите) заключается в том, что разрешение перегрузки происходит во время компиляции, а во время компиляции объект является BBase, поэтому компилятор выбирает f (A2 *). – stw

0

Ваши перегрузки в Bbase for Abase и A2 скрыты в B1. Может быть, вы можете обойти эту проблему, как это:

class Bbase{ 
public: 
    inline void f(Abase* a) { f_(a); } 
    inline void f(A1* a) { f_(a); } 
    inline void f(A2* a) { f_(a); } 
protected: 
    virtual void f_(Abase* a); 
    virtual void f_(A1* a); 
    virtual void f_(A2* a); 
}; 

class B1:public Bbase{ 
protected: 
    void f_(A1* a); 
}; 

class B2:public Bbase{ 
protected: 
    void f_(A2* a); 
}; 

или с шаблоном в BBASE:

class Bbase{ 
public: 
    template<class myA> 
    inline void f(myA* a) { f_(a); } 
protected: 
    virtual void f_(Abase* a); 
    virtual void f_(A1* a); 
    virtual void f_(A2* a); 
}; 
Смежные вопросы