层次结构中的成员函数指针
Posted
技术标签:
【中文标题】层次结构中的成员函数指针【英文标题】:Member function pointers in a hierarchy 【发布时间】:2010-04-08 17:51:36 【问题描述】:我正在使用一个定义接口的库:
template<class desttype>
void connect(desttype* pclass, void (desttype::*pmemfun)());
我有一个小的层次结构
class base
void foo();
;
class derived: public base ... ;
在derived
的一个成员函数中,我想调用
connect(this, &derived::foo);
但貌似&derived::foo
其实是base
的成员函数指针; gcc 吐出来
error: no matching function for call to ‘connect(derived* const&, void (base::* const&)())’
我可以通过将this
显式转换为base *
来解决此问题;但是为什么编译器不能将调用与desttype = base
匹配(因为derived *
可以隐式转换为base *
)?
另外,为什么是 &derived::foo
不是derived
的成员函数指针?
【问题讨论】:
是否有充分的理由使用指向成员函数的指针,而不是仅仅在基中声明一个(纯?)虚函数,并在需要时调用它? @Jerry Coffin:有的时候-有。调用纯虚函数的唯一方法是通过该函数的硬编码名称。当硬编码特定函数不可接受时,使用指向成员的指针。这一定是其中一种情况。 【参考方案1】:首先,当您执行&class::member
时,结果的类型始终基于成员实际在其中声明的类。这就是一元&
在C++ 中的工作方式。
其次,代码无法编译,因为模板参数推导失败。它从第一个参数推导出desttype = derived
,而从第二个参数推导出desttype = base
。这就是编译失败的原因。 C++中的模板实参推导规则没有考虑this
可以转换为base *
类型。此外,有人可能会争辩说,与其将this
转换为base *
类型,不如将&derived::foo
从指向基成员的指针类型转换为指向派生成员的类型。这两种方法同样可行(见下文)。
第三,C++中的成员指针遵循逆变的规则,这意味着指向基类成员的指针可以隐式转换为指向派生类成员的指针。在您的情况下,您需要做的就是通过显式指定参数来帮助编译器通过模板参数推导,并且代码应该编译
connect<derived>(this, &derived::foo);
由于&derived::foo
指针的反方差,上面应该编译,即使它是指向base
成员的指针。或者你可以这样做
connect<base>(this, &derived::foo);
由于this
指针的协方差,这也应该编译。
您还可以对实际参数使用显式强制转换(正如您在问题中提到的那样)来解决推导歧义,但在我看来,在这种情况下,显式指定的模板参数看起来更好。
【讨论】:
@AndreyT,谢谢,我没有想到明确指定模板参数。至于我的第二个问题,我想我已经根据您的解释将其拼凑起来,但我建议将您的“第一”和“第三”结合起来 - 我的猜测(虽然我可能是错的)是 原因 成员函数指针的类型基于它声明的类,即成员函数指针是逆变的,而不是协变的。如果&derived::foo
在derived
中返回一个成员函数,它就无法在base *
上调用。
有时您只需要有人在概念上加上文字并解释它们所代表的含义……伙计,我真的很喜欢这个!很好的解释。【参考方案2】:
成员函数指针在 C++ 中有很多特性,并且各种编译器在它们的工作方式上存在不一致。 Doug Clugston 的文章 "Member Function Pointers and the Fastest Possible C++ Delegates" 很好地概述了它们的工作原理(和不工作原理):
在处理派生类时, 有一些惊喜。例如, 下面的代码将在 MSVC 上编译,如果 你让 cmets 保持原样:
class SomeClass public: virtual void some_member_func(int x, char *p) printf("In SomeClass"); ; ; class DerivedClass : public SomeClass public: // If you uncomment the next line, the code at line (*) will fail! // virtual void some_member_func(int x, char *p) printf("In DerivedClass"); ; ; int main() // Declare a member function pointer for SomeClass typedef void (SomeClass::*SomeClassMFP)(int, char*); SomeClassMFP my_memfunc_ptr; my_memfunc_ptr = &DerivedClass::some_member_func; // ---- line (*)
奇怪的是,
&DerivedClass::some_member_func
是一个 类的成员函数指针SomeClass
。它不是成员DerivedClass
! (一些编译器的行为 略有不同:例如,对于 数字火星 C++,&DerivedClass::some_member_func
是 在这种情况下未定义。)但是,如果DerivedClass
覆盖some_member_func
,代码不会 编译,因为&DerivedClass::some_member_func
有 现在成为成员函数指针 班级DerivedClass
!
【讨论】:
【参考方案3】:这是模板参数推导的问题,如果模板参数没有在调用站点上明确说明,那么编译器将不会尝试进行自动转换。
根据我的经验,解决这个问题的最佳方法是为函数声明两个模板参数:
template<typename Y, typename T>
void connect(Y * pclass, void (T::*pmemfun)());
在这种情况下,编译器可以很高兴地为你自动实例化
void connect<derived, base>(derived * pclass, void (base::*pmemfun)());
这个解决方案也是非常安全的,因为从派生 * 到基 * 的转换将在 connect 内部完成(我假设你正在调用 pclass->*pmemfun() )
【讨论】:
不幸的是,我不能(至少没有过度努力)更改方法的签名。 在这种情况下,剩下的唯一解决方案就是明确指定模板参数,正如 AndreyT 所说。以上是关于层次结构中的成员函数指针的主要内容,如果未能解决你的问题,请参考以下文章