请考虑以下代码:
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()
的参数?有什么方法可以让我得到我想要的行为吗?
答案 0 :(得分:10)
通过查看参数的编译时类型来选择要调用的f
版本。此名称解析不考虑运行时类型。由于b1
的类型为Bbase*
,因此会考虑所有Bbase
个成员;采用A2*
的那个是最佳匹配,所以这是被调用的那个。
答案 1 :(得分:2)
“...选择通过将对象的this指针向上转换到基类来解析最后一行的函数调用...”。你在说什么?在所有调用中,对象指针类型为Bbase *
,调用解析为属于Bbase
或其后代的函数。编译器从不进行任何向上转换以解决您的呼叫。事实上,前两个调用需要向下转换才能调用正确的覆盖,因为覆盖属于位于层次结构中更下层的类。至于最后两个调用 - 它们通过Bbase
类型的指针分派到Bbase *
类中。类型完全匹配,不会发生任何类型的转换。
关于重载解析...重载解析是一个编译时进程,它基于参数的静态类型和可能的转换等级。您提供了A2 *
类型的参数。 f(A2 *)
候选人与您的论据正好匹配。 f(A1 *)
候选人需要从A2 *
到A1 *
的额外转化。完全匹配的候选者被认为是更好的候选者,因此它赢得了重载决策。简单。
答案 2 :(得分:1)
b1->f(static_cast<A1*>(a2));
这应该强制编译器使用带有A1类型参数的重载方法。
答案 3 :(得分:0)
它被称为名字隐藏。您在一个派生类中声明的每个f都会在其任何基类中隐藏每个可能的f。
对基类使用强制转换以获得所需的行为。
覆盖虚函数时,不要覆盖具有相同名称的重载函数。它们是不同的功能(并且在vtable中具有不同的条目)。
答案 4 :(得分:0)
Base 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);
};