双重调度会产生“隐藏虚拟功能”警告,为什么?
|
我想实现两个对象的交互,这些对象的类型派生自一个公共基类。存在默认的交互,并且一旦相同类型的对象交互,则可能会发生特定的事情。
这是使用以下双重调度方案实现的:
#include <iostream>
class A
{
public:
virtual void PostCompose(A* other)
{
other->PreCompose(this);
}
virtual void PreCompose(A* other)
{
std::cout << \"Precomposing with an A object\" << std::endl;
}
};
class B : public A
{
public:
virtual void PostCompose(A* other) // This one needs to be present to prevent a warning
{
other->PreCompose(this);
}
virtual void PreCompose(A* other) // This one needs to be present to prevent an error
{
std::cout << \"Precomposing with an A object\" << std::endl;
}
virtual void PostCompose(B* other)
{
other->PreCompose(this);
}
virtual void PreCompose(B* other)
{
std::cout << \"Precomposing with a B object\" << std::endl;
}
};
int main()
{
A a;
B b;
a.PostCompose(&a); // -> \"Precomposing with an A object\"
a.PostCompose(&b); // -> \"Precomposing with an A object\"
b.PostCompose(&a); // -> \"Precomposing with an A object\"
b.PostCompose(&b); // -> \"Precomposing with a B object\"
}
关于此代码,我有两个非常不同的问题:
您认为这是一种合理的方法吗?您会提出一些不同的建议吗?
如果省略前两个B
方法,则会收到编译器警告和错误,表明后两个B
方法隐藏了A
方法。这是为什么?不应将A*
指针强制转换为B*
指针?
更新:我刚刚发现
using A::PreCompose;
using A::PostCompose;
使错误和警告消失,但这为什么有必要?
更新2:这在这里得到了很好的解释:http://www.parashift.com/c++-faq-lite/strange-inheritance.html#faq-23.9,谢谢。那我的第一个问题呢?对这种方法有何评论?
没有找到相关结果
已邀请:
3 个回复
惭法搽
,但是自变量具有静态类型
。由于
没有以
作为参数的重载,因此调用
会将
隐式转换为
,并且您在第二个参数上只剩下一次分派。 作为一个实际的问题:为什么编译器会产生警告?为什么需要添加
指令? 原因是C ++中的查找规则。然后,编译器会遇到对
的调用,它必须查找标识符ѭ16and,并且它将从静态类型ѭ17starting开始,如果在该上下文中未能找到
,它将在层次结构中向上移动并在静态类型为
的基数。 一旦找到第一个标识符,查找将停止并尝试将函数调用与可用的重载进行匹配,如果无法匹配该调用,则会触发错误。这里重要的一点是,如果无法匹配函数调用,则查找将不会在层次结构中进一步查找。通过添加
声明,可以将基类中的标识符
带入当前作用域。 例:
当编译器遇到表达式
时,对象的静态类型为
,并且查找将检查
中的所有成员函数,标识符
位于此处,并且查找不会向上进行。唯一可用的重载参数为
,并且编译器将添加隐式转换,因此,即使可能存在最佳潜在匹配(对于该调用,ѭ28than比
更好的匹配),它将有效地调用:
下一个表达式
的处理类似,在look24ѭ中开始查找,发现那里存在成员函数。但是参数
无法隐式转换为
,即使
中存在完美匹配,编译器也会发出错误。请注意,这是调用中的错误,而不是类定义中的错误。 在处理第三个表达式时,
对象的静态类型为
(请注意,实际对象为
,但引用的类型为
),因此查找将不会在ѭ24search中进行搜索,而是从
开始。它找到两个重载,并且其中一个重载是很好的匹配,因此它将称为
。
也是如此。 最后,在最派生的类中添加不同的重载时,将重载隐藏在基数中,但它不会将其从对象中删除,因此您可以通过完全限定调用来实际调用所需的重载(这将禁用查找并具有如果函数是虚拟的,则增加了跳过动态分派的副作用),因此
将根本不执行任何查找,而只是将调用分派到
。 如果在
类中添加了
声明,则应将
中的所有ѭ26the重载添加到
中的可用重载,调用and23ѭ会考虑
中的重载,并发现最佳重载为
,因此实际上将被执行为
在许多情况下,开发人员并不总是在思考查找规则的实际工作方式,并且可能令人惊讶的是,如果没有
声明,对
的调用就会失败,或者更糟的是,在很明显的情况下,将对
的调用分派给
。过载比than42更严重。这是隐藏成员函数时编译器发出警告的原因之一。该警告的另一个很好的原因是,在许多情况下,当您实际上打算覆盖虚函数时,可能会错误地更改签名:
尝试重写成员函数
(忘记
限定词)时出现一个小错误,实际上意味着您正在创建其他函数签名。
不是对
的替代,因此通过引用ѭ37to对
的调用不会分派给
!
剿畦缄饥小
如果使用与基类包含的名称相同的名称向派生类添加新功能,并且不覆盖基类中的虚函数,则新名称将隐藏基类中的旧名称。 这就是为什么您需要通过显式编写来取消隐藏它们的原因:
取消隐藏它们的另一种方法(在这种情况下)是,从您在发布的代码中完成的基类中覆盖虚函数。我相信代码可以很好地编译。
席陋临拈