我需要能够拥有一个由继承自它的类定义的超类执行回调.我对C++相对较新,从我可以看出它看起来像成员函数指针的主题是一个非常模糊的区域.
我已经看到了问题的答案和随机博客文章,讨论各种各样的事情,但我不确定他们中是否有任何具体处理我的问题.
这是一段简单的代码,说明了我想要做的事情.这个例子可能没有多大意义,但它准确地类似于我想写的代码.
class A { protected: void doSomething(void (A::*someCallback)(int a)) { (*this.*someCallback)(1234); } }; class B : public A { public: void runDoIt() { doSomething(&B::doIt); } void runDoSomethingElse() { doSomething(&B::doSomethingElse); } protected: void doIt(int foo) { cout << "Do It! [" << foo << "]\n"; } void doSomethingElse(int foo) { cout << "Do Something Else! [" << foo << "]\n"; } }; int main(int argc, char *argv[]) { B b; b.runDoIt(); b.runDoSomethingElse(); }
小智.. 8
问题是成员函数B
不是成员函数A
,即使B
派生自A
.如果你有一个void (A::*)()
,你可以在任何一个上调用它A *
,无论指向的对象的实际派生类型如何.
(当然,同样的原则也适用于A &
.)
假设B
源自A
,并C
源自A
; 是否可以考虑void (B::*)()
(比方说)作为一个void (A::*)()
,可以做这样的事情:
A *c=new C; A *b=new B; void (A::*f)()=&B::fn;//fn is not defined in A (c->*f)();
并且B
会在类型的对象上调用成员函数C
.结果最多是无法预测的.
基于示例代码,并假设不使用类似boost的东西,我倾向于将回调构造为一个对象:
class Callback { public: virtual ~Callback() { } virtual Do(int a)=0; };
然后调用回调的函数以某种方式获取其中一个对象而不是普通的函数指针:
class A { protected: void doSomething(Callback *c) { c->Do(1234); } };
然后,您可以在每个要调用的派生函数中进行一次回调.对于doIt,例如:
class B:public A { public: void runDoIt() { DoItCallback cb(this); this->doSomething(&cb); } protected: void doIt(int foo) { // whatever } private: class DoItCallback:public Callback { public: DoItCallback(B *b):b_(b) {} void Do(int a) { b_->doIt(a); } private: B *b_; }; };
削减样板的一种显而易见的方法是将成员函数指针放入回调中,因为派生的回调可以自由处理特定类型的对象.这会使回调更通用,因为当回调它时会调用类型B的对象上的任意成员函数:
class BCallback:public Callback { public: BCallback(B *obj,void (B::*fn)(int)):obj_(obj),fn_(fn) {} void Do(int a) { (obj_->*fn_)(a); } private: B *obj_; void (B::*fn_)(int); };
这将使得像这样:
void B::runDoIt() { BCallback cb(this,&B::doIt); this->doSomething(&cb); }
这可能会被"改进",尽管不是所有读者都可以通过模板化来看待它:
templateclass GenericCallback:public Callback { public: GenericCallback(T *obj,void (T::*fn)(int)):obj_(obj),fn_(fn) {} void Do(int a) { (obj_->*fn_)(a); } private: T *obj_; void (T::*fn_)(int); };
使用它,上面的runDoIt函数可能变为:
void B::runDoIt() { GenericCallback cb(this,&B::doIt); this->doSomething(&cb); }
(泛型回调也可以在成员函数指针本身上进行模板化,尽管在大多数情况下这不太可能提供任何实际优势.它只是更多的输入.)
我发现以这种方式构造事物结果很好,因为它不需要继承.因此它对要回叫的代码几乎没有限制,这在我看来总是一件好事,而且我发现它超过了难以完全消除的冗长.不可能基于这个例子来判断这种方法是否真的适合,但......
问题是成员函数B
不是成员函数A
,即使B
派生自A
.如果你有一个void (A::*)()
,你可以在任何一个上调用它A *
,无论指向的对象的实际派生类型如何.
(当然,同样的原则也适用于A &
.)
假设B
源自A
,并C
源自A
; 是否可以考虑void (B::*)()
(比方说)作为一个void (A::*)()
,可以做这样的事情:
A *c=new C; A *b=new B; void (A::*f)()=&B::fn;//fn is not defined in A (c->*f)();
并且B
会在类型的对象上调用成员函数C
.结果最多是无法预测的.
基于示例代码,并假设不使用类似boost的东西,我倾向于将回调构造为一个对象:
class Callback { public: virtual ~Callback() { } virtual Do(int a)=0; };
然后调用回调的函数以某种方式获取其中一个对象而不是普通的函数指针:
class A { protected: void doSomething(Callback *c) { c->Do(1234); } };
然后,您可以在每个要调用的派生函数中进行一次回调.对于doIt,例如:
class B:public A { public: void runDoIt() { DoItCallback cb(this); this->doSomething(&cb); } protected: void doIt(int foo) { // whatever } private: class DoItCallback:public Callback { public: DoItCallback(B *b):b_(b) {} void Do(int a) { b_->doIt(a); } private: B *b_; }; };
削减样板的一种显而易见的方法是将成员函数指针放入回调中,因为派生的回调可以自由处理特定类型的对象.这会使回调更通用,因为当回调它时会调用类型B的对象上的任意成员函数:
class BCallback:public Callback { public: BCallback(B *obj,void (B::*fn)(int)):obj_(obj),fn_(fn) {} void Do(int a) { (obj_->*fn_)(a); } private: B *obj_; void (B::*fn_)(int); };
这将使得像这样:
void B::runDoIt() { BCallback cb(this,&B::doIt); this->doSomething(&cb); }
这可能会被"改进",尽管不是所有读者都可以通过模板化来看待它:
templateclass GenericCallback:public Callback { public: GenericCallback(T *obj,void (T::*fn)(int)):obj_(obj),fn_(fn) {} void Do(int a) { (obj_->*fn_)(a); } private: T *obj_; void (T::*fn_)(int); };
使用它,上面的runDoIt函数可能变为:
void B::runDoIt() { GenericCallback cb(this,&B::doIt); this->doSomething(&cb); }
(泛型回调也可以在成员函数指针本身上进行模板化,尽管在大多数情况下这不太可能提供任何实际优势.它只是更多的输入.)
我发现以这种方式构造事物结果很好,因为它不需要继承.因此它对要回叫的代码几乎没有限制,这在我看来总是一件好事,而且我发现它超过了难以完全消除的冗长.不可能基于这个例子来判断这种方法是否真的适合,但......
如果你可以使用boost库,我建议你使用boost :: function来完成手头的任务.
class A { public: void doSomething( boost::function< void ( int ) > callback ) { callback( 5 ); } };
那么任何继承(或外部类)都可以使用boost :: bind做一个调用:
class B { public: void my_method( int a ); }; void test() { B b; A a; a.doSomething( boost::bind( &B::my_method, &b, _1 ) ); };
我没有检查确切的语法,我从头顶输入它,但这至少接近正确的代码.