Перегруженное разрешение вызова виртуальной функции - PullRequest
7 голосов
/ 23 июня 2010

Пожалуйста, рассмотрите следующий код:

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()?Есть ли способ, которым я могу получить поведение, которое я хочу?

Ответы [ 5 ]

10 голосов
/ 23 июня 2010

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

2 голосов
/ 23 июня 2010

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

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

1 голос
/ 23 июня 2010
b1->f(static_cast<A1*>(a2));

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

0 голосов
/ 23 июня 2010

Ваши перегрузки в Bbase для 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);  
};
0 голосов
/ 23 июня 2010

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...