双调度生成“隐藏虚拟函数”警告,为什么? [英] Double dispatch produces 'hides virtual function' warnings, why?
问题描述
我想实现两个对象之间的交互,这些对象的类型是从一个公共基类派生的。有一个默认的交互和特定的事情可能发生一旦相同类型的对象交互。
这是使用以下双调度方案实现的:
#include< iostream>
class A
{
public:
virtual void PostCompose(A * other)
{
other-> PreCompose(this) ;
}
virtual void PreCompose(A * other)
{
std :: cout< 使用A对象预处理< std :: endl;
}
};
class B:public A
{
public:
virtual void PostCompose(A * other)//这一个需要出现以防止警告
{
other-> PreCompose(this);
}
virtual void PreCompose(A * other)//这需要存在以防止错误
{
std :: cout< 使用A对象预处理< std :: endl;
}
virtual void PostCompose(B * other)
{
other-> PreCompose(this);
}
virtual void PreCompose(B * other)
{
std :: cout< 使用B对象预合成< std :: endl;
}
};
int main()
{
A a;
B b;
a.PostCompose(& a); // - > Precomposing with a A object
a.PostCompose(& b); // - > Precomposing with a A object
b.PostCompose(& a); // - > Precomposing with a A object
b.PostCompose(& b); // - > 使用B对象预处理
}
我有两个,此代码:
- 您认为这是一种合理的方法吗? $
如果我省略前两个
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 ,谢谢。我的第一个问题怎么样?对此方法的任何评论?
双调度通常在C ++中实现不同,基类具有所有不同的版本使它成为一个维护噩梦,但这是语言是如何)。你尝试双重分派的问题是动态分派将找到最多派生类型 B
的对象,你正在调用该方法,但是参数有静态类型 A *
。由于 A
没有以 B *
为参数的重载,则调用 other - > PreCompose(this)
将隐式上传此
到 A *
实际问题:为什么编译器会产生警告?为什么我需要使用A :: Precompose 指令添加?
这是查找规则的原因在C ++中。然后编译器遇到对 obj.member()
的调用,它必须查找标识符 member
,它将从 obj
的静态类型开始,如果在该上下文中找不到 member
,它将向上移动在 obj
的静态类型的基础中的层次结构和查找。
一旦找到第一个标识符,lookup将停止并尝试将函数调用与可用的重载匹配,如果调用不能匹配,则将触发错误。这里重要的是,如果函数调用不能匹配,查找将不会在层次结构中向上看。通过添加使用base :: member
声明,您将标识符 member
从基类放入当前作用域。
示例:
struct base {
void foo(const char *){}
void foo(int){}
};
struct derived:base {
void foo(std :: string const&){};
};
int main(){
derived d;
d.foo(Hi);
d.foo(5);
base& b = d;
b.foo(you);
b.foo(5);
d.base :: foo(there);
}
当编译器遇到表达式 d.foo Hi);
对象的静态类型是 derived
,并且查找将检查 / code>,标识符
foo
位于那里,并且查找不会向上继续。唯一可用的重载的参数是 std :: string const&
,并且编译器将添加一个隐式转换,因此即使可能存在最佳潜在匹配( base :: foo(const char *)
是比 derived :: foo(std :: string const&)
对于该调用),它将有效地调用:
d.derived :: foo(std :: string(Hi)) ;
下一个表达式 d.foo(5);
类似地处理,查找在 derived
中开始,并且它发现有一个成员函数。但是参数 5
不能被隐式转换为 std :: string const&
,编译器会发出错误,即使在 base :: foo(int)
中存在完美匹配。注意这是调用中的错误,而不是类定义中的错误。
在处理第三个表达式时, b.foo你);
对象的静态类型是 base
(注意实际对象是派生
,但是引用的类型是 base&
),因此查找不会搜索 derived
在 base
中启动。它找到两个重载,其中一个是 good 匹配,因此它将调用 base :: foo(const char *)
。对于 b.foo(5)
也是如此。
最后,类隐藏基础中的重载,它不会从对象中删除他们,所以你可以实际上调用所需的重载完全限定调用并且如果函数是虚拟的,则具有跳过动态分派的附加副作用),因此 d.base :: foo(there)
将不执行任何查找,只需分派 base :: foo(const char *)
的调用。
如果你添加了 在很多情况下,开发人员并不总是在考虑查找规则是如何工作的,并且可能会感到惊讶的是,调用 尝试覆盖函数 I would like to implement interactions between two objects whose types are derived from a common base class. There is a default interaction and specific things may happen once objects of the same type interact.
This is implemented using the following double dispatch scheme: I have two, unfortunately quite different questions regarding this code: Update: I just found out that adding makes the errors and warnings vanish, but why is this necessary? Update 2: This is neatly explained here: http://www.parashift.com/c++-faq-lite/strange-inheritance.html#faq-23.9, thank you. What about my first question? Any comments on this approach? Double dispatch is usually implemented differently in C++, with the base class having all different versions (which makes it a maintenance nightmare, but that is how the language is). The problem with your attempt to double dispatch is that dynamic dispatch will find the most derived type As of the actual question: why is the compiler producing the warnings? why do I need to add the The reason for that are the lookup rules in C++. Then the compiler encounters a call to Once the first identifier is found, lookup will stop and try to match the function call with the available overloads, and if the call cannot be matched it will trigger an error. The important bit here is that lookup will not look further up in the hierarchy if the function call cannot be matched. By adding the Example: When the compiler encounters the expression The next expression When processing the third expression, Finally, while adding the different overloads in the most derived class hide the overloads in the base, it does not remove them from the objects, so you can actually call the overload that you need by fully qualifying the call (which disables lookup and has the added side effect of skipping dynamic dispatch if the functions were virtual), so If you had added a In many cases, developers are not always thinking on how the lookup rules actually work, and it might be surprising that the call to A small mistake while trying to override the member function 这篇关于双调度生成“隐藏虚拟函数”警告,为什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!使用base :: foo
声明到派生
类,你将添加所有重载 foo
中的可用重载的中的
,并且调用
d.foo(Hi);
会考虑 base
中的重载,发现最好的重载是 :foo(const char *);
,所以它实际上会被执行为 d.base :: foo(Hi);
p>
d.foo(5 ); > 失败,而不是
使用base :: foo
声明,或者更糟的是, d.foo Hi);
被分派到 derived :: foo(std :: string const& )
当明显是比 base :: foo(const char *)
这是编译器在您隐藏成员函数时发出警告的原因之一。这个警告的另一个好的原因是,在许多情况下,当你实际想要覆盖一个虚拟函数,你可能会错误地改变签名:
struct base {
virtual std :: string name()const {
returnbase;
};
};
struct derived:base {
virtual std :: string name(){// missing const !!!!
returnderived;
}
}
int main(){
derived d;
base& b = d;
std :: cout<< b.name()<< std :: endl; //base????
}
name
(忘记了 const
限定符)意味着你实际上创建了一个不同的函数签名。 derived :: name
是不是覆盖 base :: name
通过引用 base
到 name 将不会分派到
derived :: name
!!!
#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
methods, I get compiler warnings and errors that the last two B
methods hide the A
methods. Why is that? An A*
pointer should not be cast to a B*
pointer, or should it?using A::PreCompose;
using A::PostCompose;
B
of the object on which you are calling the method, but then the argument has static type A*
. Since A
does not have an overload that takes B*
as argument, then the call other->PreCompose(this)
will implicitly upcast this
to A*
and you are left with single dispatch on the second argument.using A::Precompose
directives?obj.member()
, it has to lookup the identifier member
, and it will do so starting from the static type of obj
, if it fails to locate member
in that context it will move up in the hierarchy and lookup in the bases of the static type of obj
.using base::member
declaration, you are bringing the identifier member
from the base class into the current scope.struct base {
void foo( const char * ) {}
void foo( int ) {}
};
struct derived : base {
void foo( std::string const & ) {};
};
int main() {
derived d;
d.foo( "Hi" );
d.foo( 5 );
base &b = d;
b.foo( "you" );
b.foo( 5 );
d.base::foo( "there" );
}
d.foo( "Hi" );
the static type of the object is derived
, and lookup will check all member functions in derived
, the identifier foo
is located there, and lookup does not proceed upwards. The argument to the only available overload is std::string const&
, and the compiler will add an implicit conversion, so even if there could be a best potential match (base::foo(const char*)
is a better match than derived::foo(std::string const&)
for that call) it will effectively call:d.derived::foo( std::string("Hi") );
d.foo( 5 );
is processed similarly, lookup starts in derived
and it finds that there is a member function there. But the argument 5
cannot be converted to std::string const &
implicitly and the compiler will issue an error, even if there is a perfect match in base::foo(int)
. Note that this is an error in the call, not an error in the class definition.b.foo( "you" );
the static type of the object is base
(note that the actual object is derived
, but the type of the reference is base&
), so lookup will not search in derived
but rather start in base
. It finds two overloads, and one of them is a good match, so it will call base::foo( const char* )
. The same goes for b.foo(5)
.d.base::foo( "there" )
will not perform any lookup at all and just dispatch the call to base::foo( const char* )
.using base::foo
declaration to the derived
class, you would add all the overloads of foo
in base
to the available overloads in derived
, and the call d.foo( "Hi" );
would consider the overloads in base
and find that the best overload is base::foo( const char* );
, so it will actually be executed as d.base::foo( "Hi" );
d.foo( 5 );
fails without the using base::foo
declaration, or worse, that the call to d.foo( "Hi" );
is dispatched to derived::foo( std::string const & )
when it is clearly a worse overload than base::foo( const char* )
. That is one of the reasons why compilers warn when you hide member functions. The other good reason for that warning is that in many cases when you actually intended to override a virtual function you might end up mistakenly changing the signature:struct base {
virtual std::string name() const {
return "base";
};
};
struct derived : base {
virtual std::string name() { // missing const!!!!
return "derived";
}
}
int main() {
derived d;
base & b = d;
std::cout << b.name() << std::endl; // "base" ????
}
name
(forgetting the const
qualifier) means that you are actually creating a different function signature. derived::name
is not an override to base::name
and thus a call to name
through a reference to base
will not be dispatched to derived::name
!!!