成员函数指针和函数对象






4.74/5 (9投票s)
2004年5月20日
4分钟阅读

76554
使用指向成员函数的指针构建一个函数对象模板,该模板绑定对象的实例和成员函数。
引言
这是一篇关于C++“指向函数的指针”和“指向成员函数的指针”的简要讨论,然后是一个关于构建函数对象模板的示例,该模板将类的实例和指向该类成员函数的指针关联到一个可传递的对象中。这可以用作STL算法或其他通用代码的谓词。
背景
在CodeProject Visual C++论坛上有一个问题促使我研究指向成员的指针。随后,我将该帖中提供的代码片段整理成一个更通用的形式,并考虑了一些更典型用法。
指向成员函数的指针
一个普通函数的指针声明(例如)如下:bool (*pfn)( int )
pfn
是指向一个接受单个整数参数并返回bool
的函数的指针。使用示例是:
bool f1 ( int i ) { return i > 10 ; } int main () { bool (*pfn)(int) ; pfn = f1 ; if ( (*pfn)( 11 )) std::cout << "pfn ( 11 ) true\n" ; return 0 ; }
pfn
的值成为CPU所理解的实际函数地址。
指向成员函数的指针是一种稍有不同的东西:
bool (Foo::*pmfn)(int) ;
pmfn
是指向类Foo
的一个成员函数的指针,该函数接受一个整数参数并返回bool
。
class Foo { private : int t_ ; public : Foo ( int t ) : t_ ( t ) { } bool f1 ( int i ) { return i > t_ ; } } ; int main () { bool (Foo::*pmfn)(int) ; pmfn = &Foo::f1 ; Foo foo ( 10 ) ; if ( (foo.*pmfn)( 11 )) std::cout << "pmfn ( 11 ) true\n" ; return 0 ; }
这显然是一段没有意义的代码,但它足以展示语法。pmfn
可以被设置为Foo
类的任何符合int
参数和bool
返回签名的成员函数。指向成员的指针的实际实现取决于类是否具有虚拟函数,在这种情况下,指针必须是虚拟函数指针表地址和其中偏移量的组合。对于一个普通的类,指针可能是一个常规的函数指针。细节取决于实现和体系结构。
函数对象
函数对象是class
或struct
的一个实例,它可以被当作函数来语法化使用。
struct PlusFunctor { int operator()( int l, int r ) { return l + r ; } } ; int main () { PlusFunctor f ; std::cout << "f ( 3, 4 ) = " << f ( 3, 4 ) << std::endl ; return 0 ; }
函数适配器
函数适配器是一种函数对象,它允许组合函数或函数对象,或者绑定参数。STL提供的一类有趣的函数适配器允许调用类的成员函数。
class SomeObject { private: int value_ ; public : SomeObject () : value_ ( 0 ) { } SomeObject ( int value ) : value_ ( value ) { } void ShowValue () { std::cout << value_ << " " ; } } ; int main () { std::vector<SomeObject> v ; v.push_back ( SomeObject ( 1 )) ; v.push_back ( SomeObject ( 2 )) ; v.push_back ( SomeObject ( 3 )) ; v.push_back ( SomeObject ( 4 )) ; std::for_each ( v.begin (), v.end (), std::mem_fun_ref ( &SomeObject::ShowValue )) ; return 0 ; }
这是以一种相当可读且简洁的方式,为集合中的每个对象调用指定的成员函数。但是,如果我们想为集合中的每个对象调用另一个对象的成员函数呢?一个场景可能是将一系列更新应用于一个文档。
class Update { private : // update data public : Update () {} // access functions } ; class Document { private : // document data public : Document () {} ; void ApplyUpdate ( const Update& update ) { } // other document functions } ;
有两种明显的方法可以应用更新:手动编码一个循环来枚举容器。
template <typename Container> void ApplyUpdatesToDocument1 ( Document& doc, Container& updates ) { Container::iterator it = updates.begin () ; while ( it != updates.end ()) { doc.ApplyUpdate ( *it ) ; ++it ; } }
通过使容器类型成为模板参数,我们可以选择使用vector、list、stack等。当你调用时,编译器可以推断出容器的类型。
int main () { std::vector<Update> vu ; vu.resize ( 10 ) ; Document doc ; ApplyUpdatesToDocument1 ( doc, vu ) ; return 0 ; }
然而,当for_each
就在那里等待帮助时,却不得不编写自己的循环似乎不那么优雅。一种方法是创建自定义函数对象,它保留文档的引用,以启动for_each
。
struct ApplyUpdateFunctor { Document& doc_ ; ApplyUpdateFunctor ( Document& doc ) : doc_ ( doc ) { } void operator () ( const Update& update ) { doc_.ApplyUpdate ( update ) ; } } ; template < typename Container> void ApplyUpdatesToDocument2 ( Document& doc, Container& updates ) { std::for_each ( updates.begin (), updates.end (), ApplyUpdateFunctor ( doc )) ; }
这很好,但我们必须为每种操作编写一个函数对象,将它们存储在某个地方,并在几天、几周或几年后回到代码时记住它们的作用。
文章的重点
通过一些模板技巧,可以编写一个通用的函数对象,像这样使用:
template <typename Container> void ApplyUpdatesToDocument3 ( Document& doc, Container& updates ) { std::for_each ( updates.begin (), updates.end (), mem_fun_bind1 ( doc, &Document::ApplyUpdate )) ; }
调用看起来比ApplyUpdatesToDocument2
复杂,但它不需要在别处定义自定义函数对象,并且动作直接写在for_each
调用中。我们想为每个更新调用doc.ApplyUpdate()
。稍后,维护工作应该可以减少在其他文件中的查找。
这是上面使用的通用函数对象代码。
template <typename C, typename Arg, typename Result> struct mem_fun_bind1_t : public std::unary_function<Arg, Result> { Result (C::*pmf_ )( Arg ) ; C& rC_ ; explicit mem_fun_bind1_t ( C& rC, Result (C::*pmf)( Arg )) : rC_ ( rC ), pmf_ ( pmf ) { } Result operator () ( Arg a ) { return (rC_.*pmf_) ( a ) ; } } ; template <typename C, typename Arg, typename Result> mem_fun_bind1_t<C, Arg, Result> mem_fun_bind1 ( C& c, Result (C::*fn)( Arg )) { return mem_fun_bind1_t <C, Arg, Result>( c, fn ) ; }
辅助函数mem_fun_bind1
生成函数对象mem_fun_bind1_t
。这允许编译器推断模板参数,并简化了必须手动编写的内容。mem_fun_bind1_t
直接类似于上面显示的ApplyUpdateFunctor
,增加了指向适当成员函数的指针,并转换为模板以允许与不同对象和成员重用。
对于具有两个参数的二元函数,等效的函数对象如下:
template <typename C, typename Arg1, typename Arg2, typename Result> struct mem_fun_bind2_t : public std::binary_function<Arg1, Arg2, Result> { Result (C::*pmf_ )( Arg1, Arg2 ) ; C& rC_ ; explicit mem_fun_bind2_t ( C& rC, Result (C::*pmf)( Arg1, Arg2 )) : rC_ ( rC ), pmf_ ( pmf ) { } Result operator () ( Arg1 a1, Arg2 a2 ) { return (rC_.*pmf_) ( a1, a2 ) ; } } ; template <typename C, typename Arg1, typename Arg2, typename Result> mem_fun_bind2_t<C, Arg1, Arg2, Result> mem_fun_bind2 ( C& c, Result (C::*fn)( Arg1, Arg2 )) { return mem_fun_bind2_t <C, Arg1, Arg2, Result>( c, fn ) ; }
这个二元版本适合原始std::sort
问题的发布者。更多(或更少)参数的扩展,如果你能找到用途,留给读者作为练习。
构建说明
本文中的所有代码都已使用Visual C++ Version 7.1构建和测试。这显然需要添加iostream
、vector
、algorithm
和functional
的适当包含文件。
没有可下载的文件,我将包含函数对象和辅助函数头文件的名称选择留给您。
后记
这是我第一次真正接触到成员函数指针。研究它们、为本文编写和测试代码片段都很有启发性。我将在未来的工作中继续使用mem_fun_bind
。我希望这篇文章即使对那些认为模板和STL令人反感的人也会有一些兴趣。
历史
编写和发布 - 2004年5月19-20日。