快速C++委托:Boost.Function 的“即插即用”替代品和多播






4.86/5 (47投票s)
2007年4月12日
33分钟阅读

304578

1959
一篇关于实现具有许多高级功能的快速C++委托的文章。
引言
已经有几个C++委托声称自己是“快速”或“最快”的委托,而Boost.Function
及其同类项Boost.Bind
和Boost.Mem_fn
已被采纳为C++标准委员会的库技术报告(TR1)的一部分。那么,这些所谓的“快速”或“最快”的委托是什么,它们比Boost.Function
“快”多少?
“快速(最快)委托”中的“快速”一词指的是“快速”调用或“快速”复制,或两者兼而有之。但是,我认为,在`“非快速”的Boost.Function`使用时,两者之间真正的问题更可能是其糟糕的复制性能。这是因为需要昂贵的堆内存分配来存储成员函数以及进行成员函数调用的绑定对象。因此,“快速”委托通常是指不需要为存储成员函数和绑定对象而进行堆内存分配的委托。在C++中,作为一种面向对象的编程范式,将委托或闭包用于成员函数和绑定对象是出现频率最高的一种实践。因此,“快速”委托在某些情况下可以大大“提升”性能。
以下四张图是三种快速委托和Boost.Function
在各种函数调用场景下的调用速度比较结果。有关详细信息,请参阅“`%FD_ROOT%/benchmark`”。
- Don的最快委托
- Sergey的快速委托
- Jae的快速委托
Boost.Function
以下两张图是三种快速委托和Boost.Function
的复制速度比较结果。对于绑定的成员函数调用,发现Boost.Function
可能比最快的花费的时间长150倍。结果可能因基准测试平台和环境而异,但很明显,在某些情况下Boost.Function
的复制性能是不可接受的。
尽管在特定情况下快速委托的速度得到了显著提升,但许多程序员并不愿意切换并开始使用快速委托。这是因为它们的功能不如Boost.Function
及其同类项丰富,而且我们已经习惯使用Boost
。这些快速委托支持的、可存储的可调用实体类型非常有限,并且大多数不支持存储函数对象,而函数对象在C++中是另一种经常出现的实践。
我曾经实现过一个快速委托,但它既不像其他快速委托那样快,也不像我认为的那样符合C++标准。事实上,后来我对其进行了修补,使其符合C++标准。这是第二个版本,但它是从头开始完全重新实现的。旧版本已过时。它是一个“快速”委托,同时也是Boost.Function
的“即插即用”替代品,并且不止于此。我说“不止于此”是因为它支持当前大多数C++委托中缺失的多播功能。它不像是一个支持多播的辅助类,而是一个类实例,可以按需充当单播和多播,而没有任何运行时性能开销。FD.Delegate
可以被认为是Boost.Function
及其同类项(Boost.Bind
和Boost.Mem_fn
)的聚合,再加上Boost.Signals
的一些功能。有关详细信息,请参阅文章末尾的“委托比较图”。
使用代码
如前所述,FD.Delegate
是Boost.Function
的“即插即用”替代品。因此,引用Boost.Function
的在线文档,特别是Boost.Function
的教程来了解FD.Delegate
的功能是合理的。只需确保将“`%FD_ROOT%/include`”添加为系统包含目录。
- 示例#1来自Boost.Function
。
#include <iostream> #include <fd/delegate.hpp> struct int_div { float operator()(int x, int y) const { return ((float)x)/y; }; }; int main() { fd::delegate<float (int, int)> f; f = int_div(); std::cout << f(5, 3) << std::endl; // 1.66667 return 0; }
- 示例#2来自Boost.Function
。
#include <iostream> #include <fd/delegate.hpp> void do_sum_avg(int values[], int n, int& sum, float& avg) { sum = 0; for (int i = 0; i < n; i++) sum += values[i]; avg = (float)sum / n; } int main() { // The second parameter should be int[], but some compilers (e.g., GCC) // complain about this fd::delegate<void (int*, int, int&, float&)> sum_avg; sum_avg = &do_sum_avg; int values[5] = { 1, 1, 2, 3, 5 }; int sum; float avg; sum_avg(values, 5, sum, avg); std::cout << "sum = " << sum << std::endl; std::cout << "avg = " << avg << std::endl; return 0; }
FD.Delegate
支持多播,并使用C#的多播语法,即operator +=
和operator -=
。
#include <iostream> #include <fd/delegate/delegate2.hpp> struct print_sum { void operator()(int x, int y) const { std::cout << x+y << std::endl; } }; struct print_product { void operator()(int x, int y) const { std::cout << x*y << std::endl; } }; int main() { fd::delegate2<void, int, int> dg; dg += print_sum(); dg += print_product(); dg(3, 5); // prints 8 and 15 return 0; }
函数指针可以进行相等性比较,但函数对象是否在编译时确定相等性比较,或者是否可以进行相等性比较,则不太确定。这一事实使得operator -=
在从多播中移除函数对象时几乎无用。FD.Delegate
提供了add()
和remove()
成员函数对来解决这个问题。add()
返回一个fd::multicast::token
实例,可用于移除添加的委托。
#include <iostream> #include <fd/delegate.hpp> #include <cassert> struct print_sum { void operator()(int x, int y) const { std::cout << x+y << std::endl; } }; struct print_product { void operator()(int x, int y) const { std::cout << x*y << std::endl; } }; struct print_difference { void operator()(int x, int y) const { std::cout << x-y << std::endl; } }; struct print_quotient { void operator()(int x, int y) const { std::cout << x/-y << std::endl; } }; int main() { fd::delegate2<void, int, int> dg; dg += print_sum(); dg += print_product(); dg(3, 5); fd::multicast::token print_diff_tok = dg.add(print_difference()); // print_diff_tok is still connected to dg assert(print_diff_tok.valid()); dg(5, 3); // prints 8, 15, and 2 print_diff_tok.remove(); // remove the print_difference delegate dg(5, 3); // now prints 8 and 15, but not the difference assert(!print_diff_tok.valid()); // not connected anymore { fd::multicast::scoped_token t = dg.add(print_quotient()); dg(5, 3); // prints 8, 15, and 1 } // t falls out of scope, so print_quotient is not a member of dg dg(5, 3); // prints 8 and 15 return 0; }
管理多个返回值一直是多播委托的一个主要关注点。Boost.Signals
的Combiner接口已被采纳,但用法和语法略有不同。Combiner接口的类型不是FD.Delegate
类型的一部分,尽管对于Boost.Signals
来说,它是声明信号变量时作为模板参数的形式。相反,FD.Delegate
有一个特殊的函数调用运算符,它将Combiner接口的实例作为最后一个函数调用参数。
#include <algorithm> #include <iostream> #include <fd/delegate.hpp> template<typename T> struct maximum { typedef T result_type; template<typename InputIterator> T operator()(InputIterator first, InputIterator last) const { if(first == last) throw std::runtime_error("Cannot compute maximum of zero elements!"); return *std::max_element(first, last); } }; template<typename Container> struct aggregate_values { typedef Container result_type; template<typename InputIterator> Container operator()(InputIterator first, InputIterator last) const { return Container(first, last); } }; int main() { fd::delegate2<int, int, int> dg_max; dg_max += std::plus<int>(); dg_max += std::multiplies<int>(); dg_max += std::minus<int>(); dg_max += std::divides<int>(); std::cout << dg_max(5, 3, maximum<int>()) << std::endl; // prints 15 std::vector<int> vec_result = dg_max(5, 3, aggregate_values<std::vector<int> >()); assert(vec_result.size() == 4); std::cout << vec_result[0] << std::endl; // prints 8 std::cout << vec_result[1] << std::endl; // prints 15 std::cout << vec_result[2] << std::endl; // prints 2 std::cout << vec_result[3] << std::endl; // prints 0 return 0; }
幕后
第一部分:存储函数指针以供以后调用,而无需堆内存分配。
根据C++标准,函数指针(包括自由函数指针和成员函数指针)不能转换为或存储到void *
。函数指针可以转换为具有不同类型签名的函数指针,但此类转换的结果不能使用;它只能转换回。成员函数的大小因平台而异,从4字节到16字节不等。为避免在存储成员函数时进行堆分配,已采用了一些著名的模板元编程技术。这些技术允许将大小小于或等于预定义通用成员函数指针大小的成员函数指针存储而不进行堆内存分配。存储的通用成员函数指针在使用前会恢复为其原始成员函数类型。
typedef void generic_fxn(); class alignment_dummy_base1 { }; class alignment_dummy_base2 { }; class alignment_dummy_s : alignment_dummy_base1 { }; // single inheritance. class alignment_dummy_m : alignment_dummy_base1, alignment_dummy_base2 { }; // multiple inheritance. class alignment_dummy_v : virtual alignment_dummy_base1 { }; // virtual inheritance. class alignment_dummy_u; // unknown (incomplete). typedef void (alignment_dummy_s::*mfn_ptr_s)(); // member function pointer of single inheritance class. typedef void (alignment_dummy_m::*mfn_ptr_m)(); // member function pointer of multiple inheritance class. typedef void (alignment_dummy_v::*mfn_ptr_v)(); // member function pointer of virtual inheritance class. typedef void (alignment_dummy_u::*mfn_ptr_u)(); // member function pointer of unknown (incomplete) class. typedef void (alignment_dummy_m::*generic_mfn_ptr)(); union max_align_for_funtion_pointer { void const * dummy_vp; generic_fxn * dummy_fp; boost::ct_if<( sizeof( generic_mfn_ptr ) < sizeof( mfn_ptr_s ) ), generic_mfn_ptr, mfn_ptr_s>::type dummy_mfp1; boost::ct_if<( sizeof( generic_mfn_ptr ) < sizeof( mfn_ptr_m ) ), generic_mfn_ptr, mfn_ptr_m>::type dummy_mfp2; boost::ct_if<( sizeof( generic_mfn_ptr ) < sizeof( mfn_ptr_v ) ), generic_mfn_ptr, mfn_ptr_v>::type dummy_mfp3; boost::ct_if<( sizeof( generic_mfn_ptr ) < sizeof( mfn_ptr_u ) ), generic_mfn_ptr, mfn_ptr_u>::type dummy_mfp4; }; BOOST_STATIC_CONSTANT( unsigned, any_fxn_size = sizeof( max_align_for_funtion_pointer ) ); union any_fxn_pointer { void const * obj_ptr; generic_fxn * fxn_ptr; generic_mfn_ptr mfn_ptr; max_align_for_funtion_pointer m_; };
当成员函数指针的大小小于或等于any_fxn_size
时,它会被存储到any_fxn_pointer
中。any_fxn_pointer
被实现为能够存储三种不同指针类型中的一种——void
数据指针、函数指针或成员函数指针——其大小小于指向多重继承类成员的函数指针。在任何特定时间只存储一种指针类型。已通过应用经过特殊处理的著名最大对齐联合技巧来处理可能导致C++标准未定义行为的对齐问题。
void hello(int, float) { } typedef void (*MyFxn)(int, float); struct foobar { void foo(int, float) { } }; typedef void (foobar::*MyMfn)(int, float); void test1(any_fxn_pointer any) { ( *reinterpret_cast<MyFxn>( any.fxn_ptr ) )( 1, 1.0f ); } void test2(any_fxn_pointer any, foobar * pfb) { ( pfb->*reinterpret_cast<MyMfn>( any.mfn_ptr ) )( 1, 1.0f ); } void main() { any_fxn_pointer any; any.fxn_ptr = reinterpret_cast<generic_fxn *>( &hello ); test1( any ); foobar fb; any.mfn_ptr = reinterpret_cast<generic_mfn_ptr>( &foobar::foo ); test2( any, &fb ); }
当成员函数指针的大小大于any_fxn_size
时(例如,在MSVC中存储指向virtual
继承类的成员函数指针),它会通过分配堆内存来存储,方式与Boost.Function
一样,作为非快速委托。然而,在实际实践中,virtual
继承很少使用。
template<typename U, typename T> void bind(UR (U::*fxn)(int, float), T t) { struct select_stub { typedef void (U::*TFxn)(int, float); typedef typename boost::ct_if<( sizeof( TFxn ) <= any_fxn_size ), typename impl_class::fast_mfn_delegate, typename impl_class::normal_mfn_delegate >::type type; }; select_stub::type::bind( *this, fxn, t, ); }
第二部分:使用any_fxn_pointer
any_fxn_pointer用于通过类模板存储任意函数指针。这样做是为了在存储函数时擦除类型,并在以后需要时安全地恢复原始类型。Sergey Ryazanov在他的文章中演示了,可以使用具有非类型成员函数模板参数的类模板来实现符合C++标准的快速成员函数委托。下面的示例大致展示了其实现方式。
class delegate { typedef void (*invoke_stub)(void const *, int); void const * obj_ptr_; invoke_stub stub_ptr_; template<typename T, void (T::*Fxn)(int)> struct mem_fn_stub { static void invoke(void const * obj_ptr, int a0) { T * obj = static_cast<T *>( const_cast<void *>( obj_ptr ) ); (obj->*Fxn)( a0 ); } }; template<typename T, void (T::*Fxn)(int) const> struct mem_fn_const_stub { static void invoke(void const * obj_ptr, int a0) { T const * obj = static_cast<T const *>( obj_ptr ); (obj->*Fxn)( a0 ); } }; template<void (*Fxn)(int)> struct function_stub { static void invoke(void const *, int a0) { (*Fxn)( a0 ); } }; public: delegate() : obj_ptr_( 0 ), stub_ptr_( 0 ) { } template<typename T, void (T::*Fxn)(int)> void from_function(T * obj) { obj_ptr_ = const_cast<T const *>( obj ); stub_ptr_ = &mem_fn_stub<T, Fxn>::invoke; } template<typename T, void (T::*Fxn)(int) const> void from_function(T const * obj) { obj_ptr_ = obj; stub_ptr_ = &mem_fn_const_stub<T, Fxn>::invoke; } template<void (*Fxn)(int)> void from_function() { obj_ptr_ = 0; stub_ptr_ = &function_stub<Fxn>::invoke; } void operator ()(int a0) const { ( *stub_ptr_ )( obj_ptr_, a0 ); } };
尽管将成员函数作为非类型模板参数传递是合法的C++特性——有些过时,但仍然被广泛使用——但编译器不支持。Sergey实现使用成员函数作为非类型模板参数的真正问题不是这些过时的编译器缺乏支持,而是其糟糕的语法。这是因为非类型模板参数不能参与函数参数的模板参数推导。因此,必须始终显式指定。
struct foobar { void foo(int) { } void bar(int) const { } }; void hello(int) { } void main() { foobar fb; foobar * pfb = &fb; delegate dg; dg.from_function<foobar, &foobar::foo>( pfb ); dg( 1 ); // (pfb->*&foobar::foo)( 1 ); dg.from_function<foobar const, &foobar::bar>( pfb ); dg( 1 ); // (pfb->*&foobar::bar)( 1 ); dg.from_function<&hello>(); dg( 1 ); // hello( 1 ); }
每次都显式提供模板参数在实践中是一个非常糟糕的主意。使用前面介绍的any_fxn_pointer
,我们可以显著改进使用语法,从而提高便利性。我们只需在委托类中添加一个any_fxn_pointer
成员来存储感兴趣的函数的地址。
class delegate
{
typedef void (*invoke_stub)(void const *, any_fxn_pointer, int);
void const * obj_ptr_;
any_fxn_pointer any_;
invoke_stub stub_ptr_;
template<typename T, typename TFxn>
struct mem_fn_stub
{
static void invoke(void const * obj_ptr, any_fxn_pointer any, int a0)
{
T * obj = static_cast<T *>( const_cast<void *>( obj_ptr ) );
(obj->*reinterpret_cast<TFxn>( any.mfn_ptr ) )( a0 );
}
};
template<typename TFxn>
struct function_stub
{
static void invoke(void const *, any_fxn_pointer any, int a0)
{
(*reinterpret_cast<TFxn>( any.fxn_ptr ) )( a0 );
}
};
public:
delegate() : obj_ptr_( 0 ), any(), stub_ptr_( 0 ) { }
template<typename T>
void from_function(void (T::*fxn)(int ), T * obj)
{
typedef void (T::*TFxn)(int);
obj_ptr_ = const_cast<T const *>( obj );
any_.mfn_ptr = reinterpret_cast<generic_mfn_ptr>( fxn );
stub_ptr_ = &mem_fn_stub<T, TFxn>::invoke;
}
template<typename T>
void from_function(void (T::*fxn)(int) const, T const * obj)
{
typedef void (T::*TFxn)(int) const;
obj_ptr_ = obj;
any_.mfn_ptr = reinterpret_cast<generic_mfn_ptr>( fxn );
stub_ptr_ = &mem_fn_stub<T const, TFxn>::invoke;
}
void from_function(void (*fxn)(int))
{
typedef void (*TFxn)(int);
obj_ptr_ = 0;
any_.fxn_ptr = reinterpret_cast<generic_fxn *>( fxn );
stub_ptr_ = &function_stub<TFxn>::invoke;
}
void operator ()(int a0) const
{
( *stub_ptr_ )( obj_ptr_, any_, a0 );
}
}; // delegate
即使这样,对于那些不支持将成员函数作为非类型模板参数的过时编译器来说,也无法正常工作。这变得更加直观,因此更容易使用,因为函数重载和自动模板参数推导现在适用。
struct foobar { void foo(int) { } void bar(int) const { } }; void hello(int) { } void main() { foobar fb; foobar * pfb = &fb; delegate dg; dg.from_function( &foobar::foo, pfb ); dg( 1 ); // (pfb->*&foobar::foo)( 1 ); dg.from_function( &foobar::bar, pfb ); dg( 1 ); // (pfb->*&foobar::bar)( 1 ); dg.from_funcion( &hello ); dg( 1 ); // hello( 1 ); }
第三部分:使用函数引用表支持丰富功能
Boost.Function
的一个有趣特性是它能够以各种形式存储成员函数以及绑定对象:a)指向被调用成员函数的目标对象类型的指针或引用,或b)任意智能指针的目标对象实例。事实上,更准确地说,这个特性属于Boost.Mem_fn
和Boost.Bind
。Boost.Function
有一个“manager
”成员,并定义了所谓的“functor_manager_operation_type
”的枚举标签。
enum functor_manager_operation_type { clone_functor_tag, destroy_functor_tag, check_functor_type_tag };
它还定义了几个标签来区分不同类型的函数。
struct function_ptr_tag {}; struct function_obj_tag {}; struct member_ptr_tag {}; struct function_obj_ref_tag {}; struct stateless_function_obj_tag {};
这是一种传统的标签分派技术,它使用函数重载来根据类型属性进行分派。这种标签分派工作得相当好,但细节有些过于复杂。这是因为特定函数类型的实现通常分散在各个地方。如果您曾经尝试查看Boost.Function
的细节,您就会明白我的意思。但是,存在一种非常简洁优雅的替代方案。它最初由Chris Diggings和Jonathan Turkanis在其关于BIL(Boost.Interface.Libarary)的文章系列中提出。我们可以首先声明一个包含一组函数指针的函数引用表。这些函数指针决定了我们要设计的委托的行为要求或概念。
class delegate
{
typedef void generic_fxn();
// Function reference table.
struct fxn_table
{
void invoke(void const *, any_fxn_pointer, int);
void copy_obj(void cons **, void const *);
void delete_obj(void const *);
bool is_empty();
};
void const * obj_ptr_;
any_fxn_pointer any_;
fxn_table const * tbl_ptr_;
然后,我们定义实现函数引用表所有条目的类模板。
template<typename T, typename TFxn> struct mem_fn_stub { static void init( void const ** obj_pptr, any_fxn_pointer & any, T * obj, TFxn fxn) { *obj_pptr = obj; any.mfn_ptr = reinterpret_cast<generic_mfn_ptr>( fxn ); } static void invoke(void const * obj_ptr, any_fxn_pointer any, int a0) { T * obj = static_cast<T *>( const_cast<void *>( obj_ptr ) ); (obj->*reinterpret_cast<TFxn>( any.mfn_ptr ) )( a0 ); } static void copy_obj(void const ** obj_pptr_dest, void const * obj_ptr_src) { *obj_pptr_dest = obj_ptr_src; // Simply copies between pointers } static void delete_obj(void const *) { // Do nothing. } static bool is_empty() { return false; } static fxn_table const * get_table() { static fxn_table const static_table = { &invoke, ©_obj, &delete_obj, &is_empty, }; return &static_table; } }; template<typename T, typename TFxn> struct mem_fn_obj_stub { static void init( void const ** obj_pptr, any_fxn_pointer & any, T * obj, TFxn fxn) { *obj_pptr = new T( *obj ); any.mfn_ptr = reinterpret_cast<generic_mfn_ptr>( fxn ); } static void invoke(void const * obj_ptr, any_fxn_pointer any, int a0) { T * obj = static_cast<T *>( const_cast<void *>( obj_ptr ) ); ( get_pointer(*obj)->*reinterpret_cast<TFxn>( any.mfn_ptr ) )( a0 ); } static void copy_obj(void const ** obj_pptr_dest, void const * obj_ptr_src) { // Clones the pointed object. *obj_pptr_dest = new T( *static_cast<T const *>( obj_ptr_src ) ); } static void delete_obj(void const * obj_ptr) { // Deletes the pointed object. delete static_cast<T const *>( obj_ptr ); } static bool is_empty() { return false; } static fxn_table const * get_table() { static fxn_table const static_table = { &invoke, ©_obj, &delete_obj, &is_empty, }; return &static_table; } }; template<typename TFxn> struct function_stub { static void init(void const ** obj_pptr, any_fxn_pointer & any, TFxn fxn) { *obj_pptr = 0; any.fxn_ptr = reinterpret_cast<generic_fxn *>( fxn ); } static void invoke(void const *, any_fxn_pointer any, int a0) { (*reinterpret_cast<TFxn>( any.fxn_ptr ) )( a0 ); } static void copy_obj(void const ** obj_pptr_dest, void const * obj_ptr_src) { *obj_pptr_dest = obj_ptr_src; // Simply copies between pointers } static void delete_obj(void const *) { // Do nothing. } static bool is_empty() { return false; } static fxn_table const * get_table() { static fxn_table const static_table = { &invoke, ©_obj, &delete_obj, &is_empty, }; return &static_table; } };
我们上面定义了三个类模板。实际上,如果不需要额外的模板参数来满足特定函数类型的行为要求,则类模板不是必需的。在这种情况下,普通类就足够了。
struct null_stub
{
static void invoke(void const *, any_fxn_pointer, int)
{
throw bad_function_call();
}
static void copy_obj(void const ** obj_pptr_dest, void const *)
{
*obj_pptr_dest = 0;
}
static void delete_obj(void const *)
{
// Do nothing.
}
static bool is_empty()
{
return true;
}
static fxn_table const * get_table()
{
static fxn_table const static_table = { &invoke, ©_obj,
&delete_obj, &is_empty, };
return &static_table;
}
};
我们现在可以实现委托类。我们仅通过函数引用表的指针tbl_ptr_
来使用我们在第一步中声明的函数引用表的条目,以实现“通用”委托本身的成员函数。public: delegate() : obj_ptr_( 0 ), any_(), tbl_ptr_( null_stub::get_table() ) { } delegate(delegate const & other) : obj_ptr_( other.obj_ptr_ ), any_( other.any_ ), tbl_ptr_( other.tbl_ptr_ ) { if( other.tbl_ptr_ ) other.tbl_ptr_->copy_obj( &obj_ptr_, other.obj_ptr_ ); } ~delegate() { if( tbl_ptr_ ) tbl_ptr_->delete_obj( obj_ptr_ ); } void swap(delegate & other) { std::swap( obj_ptr_, other.obj_ptr_ ); std::swap( any_, other.any_ ); std::swap( tbl_ptr_, other.tbl_ptr_ ); } bool empty() const { return tbl_ptr_->is_empty(); } delegate & operator =(delegate const & other) { if( this != &other ) delegate( other ).swap( *this ); return *this; } void reset() { delegate().swap( *this ); } void operator ()(int a0) const { tbl_ptr->invoke( obj_ptr_, any_, a0 ); }定义一个类来表示“null”比将函数引用表指针
tbl_ptr_
初始化为零值有几个好处。无需在运行时检查委托的nullness。如果将函数引用表指针赋值为零以表示null委托,则函数调用运算符应包含一个if子句来检查nullness。最重要的是,当在空委托上进行调用时,它还应有一个异常语句来抛出异常,这似乎是不合理的惩罚。由于委托的nullness是在编译时通过引入“null”类null_stub
确定的,因此我们可以通过不检查运行时nullness并且在不是“null”委托时避免抛出异常来提高性能。最后,通过添加接口成员函数来支持从各种函数类型存储,我们可以完成我们新的委托类。
template<typename T, typename U>
void from_function(void (U::*fxn)(int), T obj)
{
reset();
typedef void (U::*TFxn)(int);
struct select_stub
{
typedef typename boost::ct_if<
(::boost::is_pointer<T>::value),
mem_fn_stub<typename boost::remove_pointer<T>::type, TFxn>,
mem_fn_obj_stub<T, TFxn>
> type;
};
select_stub::type::init( &obj_ptr_, any_, obj, fxn );
tbl_ptr_ = select_stub::type::get_table();
}
template<typename T, typename U>
void from_function(void (U::*fxn)(int) const, T obj)
{
reset();
typedef void (U::*TFxn)(int) const;
struct select_stub
{
typedef typename boost::ct_if<
(::boost::is_pointer<T>::value),
mem_fn_stub<typename boost::remove_pointer<T>::type, TFxn>,
mem_fn_obj_stub<T, TFxn>
> type;
};
select_stub::type::init( &obj_ptr_, any_, obj, fxn );
tbl_ptr_ = select_stub::type::get_table();
}
void from_function(void (*fxn)(int))
{
reset();
typedef void (*TFxn)(int);
function_stub<TFxn>::init( &obj_ptr_, any_, fxn );
tbl_ptr_ = function_stub<TFxn>::get_table();
}
}; // class delegate
我们刚刚为委托添加了智能指针支持。现在,我们可以使用任何类型的智能指针来绑定被调用成员函数的对象,只要智能指针的get_pointer()
重载在可见的命名空间范围内可用。
struct foobar
{
void foo(int) { }
void bar(int) const { }
};
template<typename T>
T * get_pointer(boost::shared_ptr<T> const & t)
{
return t.get();
}
void main()
{
boost::shared_ptr<foobar> spfb(new foobar);
delegate dg;
dg.from_function( &foobar::foo, spfb );
dg( 1 ); // (get_pointer(spfb)->*&foobar::foo)( 1 );
}
如上所示,添加对新函数类型的支持非常简单。首先,它根据我们感兴趣存储的函数类型的特性确定所有行为要求。然后,它添加一个类模板,并开始为新函数类型按要求定义函数引用表的所有条目。最后,它添加一个接口函数重载——例如上面的from_function()
成员函数——以支持新添加的函数类型。
如果我们想为委托添加更多的行为要求或概念,我们可以向函数引用表中添加新的条目来实现此目的。当然,所有已为其他函数类型定义的现有类模板都必须相应地实现新添加的条目。否则,将断言编译错误。请注意,即使这些新添加的条目不适用于某些已为特定函数类型定义的类模板,它们仍然必须定义空的条目,这些条目可能什么都不做,因此可能被编译器优化掉。
从上面的示例中可以很容易地看出,函数引用表中的所有条目都以类型中立的形式声明。这意味着它可能由函数调用类型签名决定,但仅此而已。此外,我们还可以看到void指针作为参数传递,并且特定类型信息作为实现函数引用表所有条目的类模板的模板参数给出。这些传入传出的void指针通过强制转换为类模板的模板参数之一而“活”过来并恢复其原始身份。然而,它无疑只会在其存储生命周期内擦除类型信息,并在需要时恢复为正确的身份。这是如何以类型安全的方式实现委托对特定函数类型的不同行为要求集,但具有类型中立持久性的关键点。
实现细节将在一个位置定义,即特定函数类型的类模板,因此它比标签分派技术具有更好的可读性和可管理性。它就像一个“策略模式”,使得能够封装委托对各种函数类型的可互换算法。实际上,函数引用表是一个接口,而该技术可以被认为是C++虚表特性的静态类型或静态绑定版本。
每一个实现函数引用表所有条目的类模板,根据其对特定函数类型的需求,彼此之间都是正交的。这意味着在设计一个存储成员函数的类模板时,我们不需要关心函数对象是如何存储到委托中的,反之亦然。那么这意味着什么呢?我现在可以在一个单一的委托类定义中更容易地实现和支持多播委托。运行时性能不会有任何下降,非多播委托的大小也不会增加。这是因为它们——即多播委托和非多播委托——被视为同名但完全不同的事物。
因此,为通用委托声明函数引用表的条目以泛化我们感兴趣的从给定函数类型存储的通用委托的行为要求,以及如何基于函数类型的独特特征和要求对其进行分类,变得非常重要。
为特定函数类型选择合适的函数引用表发生在委托类接口函数重载的实现中。它通常涉及模板元编程和类型特征来确定或转换函数类型。不幸的是,我在接口函数重载中实现选择结构时使用的一些类型特征仅在基于C++模板部分特化功能的区域工作(例如,boost::remove_pointer<>
)。这就是为什么FD.Delegate
在某些不支持或支持不完整的过时编译器上无法工作的原因。如果我只支持有限的功能集,如Don的最快委托或Sergey的快速委托,我也许可以支持许多过时的编译器。然而,我希望它成为Boost.Function
的“即插即用”替代品,所以这不是一个选项。
D. 函数类型的分类。
C++世界中有三种主要的调用实体。
- 自由函数
- 成员函数
- 函数对象
这些基本的调用实体根据其绑定对象或函数对象如何在内部存储和管理,被细分为更精细的种类。请参阅下表。
函数类型 | 类别 | 描述 |
---|---|---|
FT01。 | 1. | 自由函数(包括静态成员函数) |
FT02。 | 2-a。 | 指向函数所属类型的对象的成员函数。 |
FT03。 | 2-b。 | 指向函数所属类型的对象的引用上的成员函数。 |
FT04。 | 2-c。 | 成员函数,绑定到属于该函数所属类型(或支持get_pointer() 重载的任何类型,即智能指针)的对象副本。 |
FT05。 | 2-d。 | 绑定到指向或引用函数所属类型对象的成员函数。 |
FT06。 | 2-e。 | 绑定到支持get_pointer() 重载的任何类型(例如智能指针)的对象副本的成员函数。 |
FT07。 | 3-a。 | 指向函数对象的指针或引用。 |
FT08。 | 3-b。 | 函数对象的副本。 |
FT09。 | 3-c。 | 无状态函数对象。 |
FT10。 | 空委托。 | |
FT11。 | 多播委托。 |
在之前的委托示例中,我们添加了对函数类型FT01、FT05、FT06和FT10的支持。为了更容易理解这些函数类型是什么,请参阅下面的示例。
struct foobar { int id_; void foo(int) { } static void bar(int) { } void operator ()(int) const { } }; void hello(int) { } struct stateless { void operator ()(int) const { } }; void main() { delegate<void (int)> dg1; foobar fb; foobar * pfb = &fb; boost::shared_ptr<foobar> spfb( new foobar ); dg1 = &hello; // FT01 dg1( 1 ); // hello( 1 ); dg1 = &foobar::bar; // FT01 dg1( 1 ); // foobar::bar( 1 ); delegate<void (foobar *, int)> dg2; dg2 = &foobar::foo; // FT02 dg2( pfb, 1 ); // (pfb->*&foobar::foo)( 1 ); delegate<void (foobar &, int)> dg3; dg3 = &foobar::foo; // FT03 dg3( fb, 1 ); // (fb.*&foobar::foo)( 1 ); delegate<void (foobar, int)> dg4; dg4 = &foobar::foo; // FT04 dg4( fb, 1 ); // ((copy of fb).*&foobar::foo)( 1 ); delegate<void (boost::shared_ptr<foobar>, int)> dg5; dg5 = &foobar::foo; // FT04 dg5( spfb, 1 ); // (get_pointer(spfb)->*&foobar::foo)( 1 ); dg1.bind( &foobar::foo, pfb ); // FT05 dg1( 1 ); // (pfb->*&foobar::foo)( 1 ); dg1.bind( &foobar::foo, boost::ref( fb ) ); // FT05 dg1( 1 ); // (fb.*&foobar::foo)( 1 ); dg1.bind( &foobar::foo, spfb ); // FT06 dg1( 1 ); // (get_pointer(spfb)->*&foobar::foo)( 1 ); dg1 = pfb; // FT07 dg1( 1 ); // (*pfb)( 1 ); dg1 = boost::ref( fb ); // FT07 dg1( 1 ); // fb( 1 ); dg1 = fb; // FT08 dg1( 1 ); // (copy of fb)( 1 ); dg1 = stateless(); // FT09 dg1( 1 ); // stateless()( 1 ); dg1 = 0; // FT10 try { dg1( 1 ); } // throw bad_function_call(); catch(bad_function_call) { } dg1 += &hello; // FT11 dg1 += delegate<void (int)>( &foobar::foo, spfb ); dg1 += fb; dg1( 1 ); // hello( 1 ); // (get_pointer(spfb)->*&foobar::foo)( 1 ); // (copy of fb)( 1 ); }
E部分:函数引用表条目列表
如前所述,为通用委托声明通用且代表性的函数引用表条目非常重要。它是整个FD.Delegate
设计的基石,并决定了性能和健壮性。以下函数条目列表是为了泛化我们上面分类的各种函数类型的通用委托的通用行为要求。
调用
invoke()
- 调用底层可调用实体。
对象管理
copy_obj()
- 从源复制对象到目标。delete_obj()
- 删除对象。
一般信息查询
is_empty()
- 确定委托是否为空。size_of_fxn()
- 获取底层可调用实体的尺寸。type_of_fxn()
- 获取底层可调用实体的std::type_info
。is_functor()
- 确定底层可调用实体是否为函数对象。
比较
compare_equal_same_type()
- 比较同一类型的两个底层可调用实体的相等性。memcmp_delegate()
- 任意类型两个底层可调用实体之间的非上下文内存比较。
多播
is_multicast()
- 确定委托是否为多播。add_delegates()
- 将一个或多个委托添加到多播中。remove_delegates()
- 从多播中移除一个或多个委托。find_delegate()
- 确定指定委托是否存在于多播中。
F部分:用于存储可调用实体的通用数据结构
至少需要两个void指针和一个any_fxn_pointer
才能将前面分类的所有函数类型存储到通用委托中。为了方便访问和操作这些void指针以及any_fxn_pointer
,它们被捆绑在一个名为“delegate_holder
”的结构中。
struct delegate_holder { void const * obj_ptr; any_fxn_pointer any; void const * tbl_ptr; };
请参阅下表,了解在存储各种函数类型时如何有效地使用这些void指针成员。
函数类型 | delegate_holder | 快速委托(是/否) |
与自身类型的相等性比较(compare_eq_same_type) |
||
---|---|---|---|---|---|
.obj_ptr | .any | .tbl_ptr | |||
FT01 | 未使用 | 函数指针 | 指向函数引用表的指针 | 是 | 比较 any.fxn_ptr |
FT02 | 未使用 | 成员函数指针 | 指向函数引用表的指针 | 是 | 比较 any.mfn_ptr |
FT03 | 未使用 | 成员函数指针 | 指向函数引用表的指针 | 是 | 比较 any.mfn_ptr |
FT04 | 未使用 | 成员函数指针 | 指向函数引用表的指针 | 是 | 比较 any.mfn_ptr |
FT05 | 指向绑定对象的指针 | 成员函数指针 | 指向函数引用表的指针 | 是 | 依次比较 any.mfn_ptr、obj_ptr 和 *obj_ptr |
FT06 | 指向堆分配的绑定对象的指针 | 成员函数指针 | 指向函数引用表的指针 | 否 | 依次比较 any.mfn_ptr、get_pointer(*obj_ptr) 和 *get_pointer(*obj_ptr) |
FT07 | 指向函数对象的指针 | 未使用 | 指向函数引用表的指针 | 是 | 依次比较 obj_ptr 和 *obj_ptr |
FT08 | 指向堆分配的函数对象的指针 | 未使用 | 指向函数引用表的指针 | 否 | 比较 *obj_ptr |
FT09 | 未使用 | 未使用 | 指向函数引用表的指针 | 是 | 始终为TRUE |
FT10 | 未使用 | 未使用 | 指向函数引用表的指针 | 是 | 始终为TRUE |
FT11 | 指向堆分配的delegate_holder列表的指针 | 未使用 | 指向函数引用表的指针 | 否 | for_each (delegate_holder列表) 逐个比较_eq_same_type |
G部分:支持多播
为了支持多播,模拟EqualityComparable
概念以能够比较两个相同类型的委托非常重要。如Boost.Function
的常见问题解答所述,众所周知,目前没有合理的方法来区分特定函数对象是否具有可访问的相等性比较运算符。由于Boost.Function
和FD.Delegate
擦除了类型信息并在需要时恢复它,因此无论是否实际执行了两种任意函数类型委托之间的比较,或者是否强制使用了相等性比较运算符(当该运算符不可用或不适用于指定的函数对象类型时),都会断言编译错误。
显然,当我们不比较委托时,导致编译错误是不可接受的。因此,FD.Delegate
默认返回false
,并且在比较存储函数对象作为其底层可调用实体的相同类型的两个委托时,不使用相等性比较运算符。然而,如果满足某个特定条件,可以将其更改为返回特定函数对象的相等性比较运算符的结果,从而可能为true
。稍后将对此进行解释。顺便说一句,当它们的可调用实体都是自由函数或成员函数时,比较相同类型的两个委托没有问题,因为相同类型的函数指针在C++中是相等性可比较的。
如前所述,如果满足某个特定条件,可以使用相等性比较运算符来比较相同函数对象类型的两个委托,而不是盲目返回false
。用户可以通过为函数对象类型定义fd::is_equality_comparable<>
的模板特化,或者可能通过使用为此目的提供的更简单的宏定义FD_DELEGATE_EQUALITY_COMPARABLE_TYPE
来手动指示特定函数对象类型具有可访问的相等性比较运算符。FD.Delegate
将使用相等性比较运算符,并根据运算符的实现返回比较结果。
struct make_int { make_int(int n, int cn) : N(n), CN(cn) {} int operator()() { return N; } int operator()() const { return CN; } int N; int CN; }; bool operator ==(make_int const & lhs, make_int const & rhs) { return lhs.N == rhs.N; } FD_DELEGATE_EQUALITY_COMPARABLE_TYPE(make_int); // (A) void main() { delegate0<void> dg1, dg2; dg1 = make_int( 1, 10 ); dg2 = make_int( 1, 20 ); assert( dg1.equal_to( dg2 ) == true ); // (B) }
如果注释掉第(A)行,断言语句(B)将始终保持false
,永不变为true
,如前所述。另请注意,我们不使用相等性比较运算符来比较FD.Delegate
。这是因为出于与上述相同的原因,它在Boost.Function
中不是一个有效的表达式。此外,FD.Delegate
是Boost.Function
的“即插即用”替代品。因此,我们使用equal_to()
成员函数来比较两个委托。下面的代码片段是比较两个委托执行顺序的伪代码示例。
// Exposition purpose only.
bool delegate::equal_to(delegate const & other)
{
if( this->type_of_fxn() != other.type_of_fxn() )
then return false
if( this->get_holder().any.mfn_ptr != other.get_holder().any.mfn_ptr )
then return false;
if( this->get_holder().obj_ptr == other.get_holder().obj_ptr )
then return true;
return this->compare_equal_same_type( this->get_holder(),
other.get_holder() );
}
compare_equal_same_type()
是前面解释的函数引用表的条目之一。因此,不同函数类型的类模板可以根据其自身的要求实现不同的比较。所有指定函数类型需要比较同一函数对象类型的类模板都将实现此类比较以返回false
。否则,将为指定的函数对象类型定义fd::is_equality_comparable<>
特化,以指示相等性比较运算符的可用性。
不幸的是,即使这种有限的相等性比较支持也不适用于比较同一类型的两个委托,用于我们在日常STL编程中经常遇到的匿名函数对象。定义fd::is_equality_comparable<>
特化用于匿名函数对象并非不可能,但也不切实际。Boost.Signals
通过提供connect()
方法返回一个名为“connection
”的对象来解决此问题。“connection
”对象可用于断开已建立的连接或检查连接的有效性。FD.Delegate
也是如此,但名称标签略有不同。FD.Delegate
有一个名为add()
的成员函数,它几乎等同于operator +=
,但它们的返回类型不同。
operator +=
和operator -=
返回自身引用,而add()
返回一个名为“token
”的对象,remove()
返回多播委托中的委托数量。“token
”与Boost.Signals
的“connection
”非常相似。它可用于移除之前添加的一个或多个委托,或检查“token
”本身的有效性。
void main() { delegate1<int, int> dg1; dg1 += std::negate<int>(); token tk1 = dg1.add( std::bind1st( std::plus<int>(), 3 ) ); // (A) assert( tk1.valid() == true ); assert( dg1.count() == 2 ); dg1.remove( std::bind1st( std::plus<int>(), 3 ) ); // Can not remove the delegate added in the // line (A) std::bind1st( std::plus<int>(), 3 ) assert( dg1.count() == 2 ); tk1.remove(); // Removes the delegate added in the line // (A) std::bind1st( std::plus<int>(), 3 ) assert( dg1.count() == 1 ); }
H部分:Combiner接口
C#委托中缺少的是多播调用的多个返回值管理。Boost.Signals
采用一种称为Combiner接口的技术,有效地服务于两个目的:a)多个返回值管理;b)多个调用控制。有关Boost.Signals
的Combiner接口,请参阅其文档。
Boost.Signals
的Combiner接口的思想和大部分实现细节被复制并整合到FD.Delegate
中。然而,它们在多播委托中的用法上有一个很大的区别。为了与Boost.Function
保持一致,FD.Delegate
的Combiner接口不像Boost.Signals
那样设计成委托类类型的一部分(以模板参数的形式)。相反,FD.Delegate
引入了一个特殊的函数调用运算符,它将Combiner接口的实例作为最后一个函数调用参数。请参阅下面的示例。
struct maximum { typedef int result_type; template<typename InputIterator> int operator()(InputIterator first, InputIterator last) const { if(first == last) return 0; int max = *first++; for(; first != last; ++first) max = (*first > max)? *first : max; return max; } }; void main() { delegate2<int, int, int> dg1; dg1 += std::plus<int>(); dg1 += std::multiplies<int>(); dg1 += std::minus<int>(); dg1 += std::divides<int>(); int max = dg1( 5, 3, maximum() ); assert( max == 15 ); }
虽然Combiner接口在大多数情况下用于管理多播调用的多个返回值,但它也可以通过multicast_call_iterator
来控制多个调用本身,该迭代器是从Boost.Signals
的slot_call_iterator
复制和修改而来的。请参阅下面的示例。如果提供的Combiner接口要求此类操作条件,则可以中止多播中所有委托的调用,或跳过某些委托的调用。
template<typename T> struct first_positive { typedef T result_type; template<typename InputIterator> T operator()(InputIterator first, InputIterator last) const { while (first != last && !(*first > 0)) // Aborts if the result is the first positive. { ++first; } return (first == last) ? 0 : *first; } }; template<typename T> struct noisy_divide { typedef T result_type; T operator()(T const & x, T const & y) const { std::cout << "Dividing " << x << " and " << y << std::endl; return x/y; } }; int main() { fd::delegate2<int, int, int> dg_positive; dg_positive += std::plus<int>(); dg_positive += std::multiplies<int>(); dg_positive += std::minus<int>(); dg_positive += noisy_divide<int>(); assert(dg_positive(3, -5, first_positive<int>()) == 8); // returns 8, but prints nothing. return 0; }
为Boost.Signals
实现的任何Combiner接口都无需修改即可用于FD.Delegate
。很明显,当FD.Delegate
作为多播委托运行时,它支持Boost.Signals
的许多有用功能。然而,它绝不是为了取代Boost.Signals
。FD.Delegate
中没有实现Boost.Signals
的一些非常复杂的特性,但当它作为Boost.Function
的“即插即用”替代品时,它可以提供比通用多播委托更多的功能。
委托比较图
功能描述 | Don.FD | Sergey.FD | Jae.FD | Boost.Function | Boost.Signals |
---|---|---|---|---|---|
自由函数(包括静态成员函数)/ FT01 | 是 | 是 | 是 | 是 | 是 |
指向函数所属类型对象的成员函数 / FT02 | 否 | 否 | 是 | 是 | 是 |
指向函数所属类型对象的引用上的成员函数 / FT03 | 否 | 否 | 是 | 是 | 是 |
成员函数,绑定到属于该函数所属类型(或支持get_pointer() 重载的任何类型,即智能指针)的对象副本 / FT04 |
否 | 否 | 是 | 是 | 是 |
绑定到指向函数所属类型对象的成员函数 / FT05 | 是 | 是 | 是 | 是 | 是 |
绑定到支持get_pointer() 重载的任何类型(例如智能指针)的对象副本的成员函数 / FT06 |
否 | 否 | 是 | 是 | 是 |
指向函数对象的指针 / FT07 | 否 | 否 | 是 | 是 | 是 |
函数对象类型的对象副本 / FT08 | 否 | 否 | 是 | 是 | 是 |
无状态函数对象 / FT09 | 否 | 否 | 是 | 是 | 是 |
空委托调用时抛出bad_function_call异常 / FT10 | 可能 | 可能 | 是 | 是 | N/A |
多播委托 / FT11 | 否 | 否 | 是 | 否 | 是 |
Combiner接口管理多播的多个返回值 | N/A | N/A | 是 | N/A | 是 |
Combiner接口控制多播的调用(xxx_call_iterator) | N/A | N/A | 是 | N/A | 是 |
连接管理(多播) | N/A | N/A | 是 | N/A | 是 |
按顺序调用槽组(多播) | N/A | N/A | 否 | N/A | 是 |
命名槽(多播) | N/A | N/A | 否 | N/A | 是 |
Trackable支持(多播) | N/A | N/A | 否 | N/A | 是 |
宽松的函数类型签名 | 否 | 否 | 是 | 是 | 是 |
快速成员函数调用委托 | 是 | 是 | 是 | 否 | 否 |
对象大小(32位系统) | 8或12字节 | 8 字节 | 16字节 + α | 12字节 + α | 36字节 + α |
存储成员函数时对象的大小(32位系统) | 8或12字节 | 8 字节 | 16字节 | 12字节 + α | 36字节 + α |
存储成员函数时复制速度 | ●●●●● | ●●●●● | ●●●●○ | ●○○○○ | ●○○○○ |
调用速度 | ●●●●● | ●●●●○ | ●●●●○ | ●●●○○ | ●●●○○ |
与自身类型相等性可比 | 是 | 否 | 支持 | 否 | 否 |
与任意函数类型(非自身类型)相等性可比 | 否 | 否 | 是 | 是 | 否 |
小于可比 | 是 | 否 | 是 | 否 | 否 |
自定义分配器 | N/A | N/A | 是 | 是 | 是 |
调用约定(__stdcall、__fastcall、__cdecl) | 否 | 否 | 是 | 是 | 是 |
Boost.Function 即插即用替代品 |
否 | 否 | 是 | 是 | 否 |
符合C++标准 | 否 | 是 | 是 | 是 | 是 |
可移植性 | ●●●●● | ●●○○○ | ●●●●○ | ●●●●● | ●●●●● |
Boost依赖 | 否 | 否 | 是 | 是 | 是 |
辅助函数模板和类模板
FD.Bind
FD.Bind
是一组辅助函数重载,它们返回FD.Delegate
的实例。与Boost.Bind
不同,FD.Bind
只能用于将成员函数调用的对象与成员函数绑定。FD.Bind
可以返回函数类型FT05或FT06的FD.Delegate
实例,其中FT05是快速委托函数类型。当然,FD.Delegate
与Boost.Bind
也能完美配合。
#include <fd/delegate.hpp> #include <fd/delegate/bind.hpp> #include <boost/shared_ptr.hpp> #include <boost/ref.hpp> struct foobar { void foo(int) { } }; void main() { fd::delegate<void (int)> dg1; foobar fb; foobar * pfb = &fb; boost::shared_ptr<foobar> spfb( new foobar ); dg1.bind( &foobar::foo, pfb ); // FT05 dg1 = fd::bind( &foobar::foo, pfb ); // FT05 dg1( 1 ); // (pfb->*&foobar::foo)( 1 ); dg1.bind( &foobar::foo, boost::ref( fb ) ); // FT05 dg1 = fd::bind( &foobar::foo, boost::ref( fb ) ); // FT05 dg1( 1 ); // (fb.*&foobar::foo)( 1 ); dg1.bind( &foobar::foo, fb ); // FT06 dg1 = fd::bind( &foobar::foo, fb ); // FT06 dg1( 1 ); // ((copy of fb).*&foobar::foo)( 1 ); dg1.bind( &foobar::foo, spfb ); // FT06 dg1 = fd::bind( &foobar::foo, spfb ); // FT06 dg1( 1 ); // (get_pointer(spfb)->*&foobar::foo)( 1 ); }
FD.Resolution
当支持宽松的函数类型签名时,库会变得更有用。然而,便利性的代价出现了一个问题。请参阅下面的示例。struct foobar { long hello(long) { return 0; } int foo(int) { return 0; } // (A) long foo(long) { return 0; } // (B) int bar(int) { return 0; } // (C) int bar(long) { return 0; } // (D) }; void main() { boost::function<int (foobar *, int)> fn; fn = &foobar::hello; // Compile Okay, relaxed function type signature. // Implicitly convertible from 'int' to 'long'. fn = &foobar::foo; // Ambiguity due to the support for the // relaxed function type signature. // (A) or (B) ? my_delegate_do_not_support_relaxed<int (foobar *, int)> md; md = &foobar::hello; // Compile error. md = &foobar::foo; // No problem, choose (A). }
Boost
的许多库以及FD.Delegate
都存在上述歧义问题。它们通常提供最小但却不完整或不方便的设备来解决这个问题。您可以通过在函数调用语法中指定返回类型或参数类型来显式指定它们,以帮助重载解析。
void main() { foobar fb; foobar * pfb = &fb; boost::bind<int>( &foobar::foo, pfb, _1 ); // (A) boost::bind<long>( &foobar::foo, pfb, _1 ); // (B) // boost::bind<int, ???>( &foobar::bar, pfb, _1 ); // Can't solve the ambiguity. boost::mem_fn<int>( &foobar::foo ); // (A) boost::mem_fn<long>( &foobar::foo ); // (B) // boost::mem_fn<int, ???>( &foobar::bar ); // Can't solve the ambiguity. boost::function<int (int)> fn; fd::bind<int>( &foobar::foo, pfb ); // (A) fd::bind<long>( &foobar::foo, pfb ); // (B) fd::bind<int, int>( &foobar::bar, pfb ); // (C) fd::bind<int, long>( &foobar::bar, pfb ); // (D) fd::delegate<int (int)> dg; dg = fd::bind<int, int>( &foobar::bar, pfb ); // (C) dg = fd::bind<int, long>( &foobar::bar, pfb ); // (D) dg.bind<int, int>( &foobar::bar, pfb ); // (C) dg.bind<int, long>( &foobar::bar, pfb ); // (D) }
FD.Resolution
是一个小巧的实用类模板,在这些情况下非常方便,可以解决重载解析的歧义。它更加通用和直观,因为您无需查阅单个库的详细信息即可确定模板参数的顺序,以便在正确的位置指定参数类型。请注意,Boost.Bind
和Boost.Mem_fn
只允许您指定返回类型来帮助函数重载解析。另请注意,FD.Resolution
是一个独立的实用类,因此可以不使用FD.Delegate
。
#include <fd/resolution.hpp> using fd::resolution; void main() { foobar fb; foobar * pfb = &fb; boost::bind( resolution<int (int)>::select( &foobar::foo ), pfb, _1 ); // (A) boost::bind( resolution<long (long)>::select( &foobar::foo ), pfb, _1 ); // (B) boost::bind( resolution<int (int)>::select( &foobar::bar ), pfb, _1 ); // (C) boost::bind( resolution<int (long)>::select( &foobar::bar ), pfb, _1 ); // (D) boost::mem_fn( resolution<int (int)>::select( &foobar::foo ) ); // (A) boost::mem_fn( resolution<long (long)>::select( &foobar::foo ) ); // (B) boost::mem_fn( resolution<int (int)>::select( &foobar::bar ) ); // (C) boost::mem_fn( resolution<int (long)>::select( &foobar::bar ) ); // (D) boost::function<int (int)> fn; fn = boost::bind( resolution<int (int)>::select( &foobar::bar ), pfb, _1 ); // (C) fn = boost::bind( resolution<int (long)>::select( &foobar::bar ), pfb, _1 ); // (D) fd::bind( resolution<int (int)>::select( &foobar::foo ), pfb ); // (A) fd::bind( resolution<long (long)>::select( &foobar::foo ), pfb ); // (B) fd::bind( resolution<int (int)>::select( &foobar::bar ), pfb ); // (C) fd::bind( resolution<int (long)>::select( &foobar::bar ), pfb ); // (D) fd::delegate<int (int)> dg; dg = fd::bind( resolution<int (int)>::select( &foobar::bar ), pfb ); // (C) dg = fd::bind( resolution<int (long)>::select( &foobar::bar ), pfb ); // (D) dg.bind( resolution<int (int)>::select( &foobar::bar ), pfb ); // (C) dg.bind( resolution<int (long)>::select( &foobar::bar ), pfb ); // (D) }
回归测试
有关详细信息,请参阅“`%FD_ROOT%/libs/delegate/test`”。FD.Delegate
是基于Boost 1.33.1
构建的。如前所述,FD.Delegate
使用了Boost
的几个类型特征类来转换类型信息。这些特性,特别是remove_xxx
,需要编译器支持模板的部分特化。VC6和VC7不支持或对模板的部分特化支持不完整。但是,有一些。
测试名称 | 测试 类型 |
Intel C++ 8.1 WIN32 |
Intel C++ 9.1 WIN32 |
MinGW 3.4.2 | GNU gcc 3.4.43 |
MSVC++ 6sp5 |
MSVC++ 6sp5 #2 (3) |
MSVC++ 2003sp1 |
MSVC++ 2005sp1b |
---|---|---|---|---|---|---|---|---|---|
bind_cdecl _mf_test |
run | 失败 (1) | 失败 (1) | 失败 (2) | 失败 (2) | 失败 | 通过 | 通过 | 通过 |
bind_eq _test |
run | 通过 | 通过 | 通过 | 通过 | 失败 | 通过 | 通过 | 通过 |
bind _fastcall _mf_test |
run | 通过 | 通过 | 失败 (2) | 失败 (2) | 失败 | 通过 | 通过 | 通过 |
bind _function _test |
run | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 |
bind _stdcall _mf_test |
run | 通过 | 通过 | 失败 (2) | 失败 (2) | 失败 | 通过 | 通过 | 通过 |
mem_fn _cdecl_test |
run | 失败 (1) | 失败 (1) | 失败 (2) | 失败 (2) | 通过 | 通过 | 通过 | 通过 |
mem_fn _derived _test |
run | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 |
mem_fn _eq_test |
run | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 |
mem_fn _fastcall _test |
run | 通过 | 通过 | 失败 (2) | 失败 (2) | 通过 | 通过 | 通过 | 通过 |
mem_fn _stdcall _test |
run | 通过 | 通过 | 失败 (2) | 失败 (2) | 通过 | 通过 | 通过 | 通过 |
mem_fn _test |
run | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 |
mem_fn _void_test |
run | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 |
empty _delegate |
run | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 |
allocator _test |
run | 通过 | 通过 | 通过 | 通过 | 失败 | 失败 | 通过 | 通过 |
contains2 _test |
run | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 |
contains _test |
run | 通过 | 通过 | 通过 | 通过 | 失败 | 失败 | 通过 | 通过 |
delegate _30 |
compile | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 |
delegate _arith _cxx98 |
run | 通过 | 通过 | 通过 | 通过 | 失败 | 失败 | 通过 | 通过 |
delegate _arith _portable |
run | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 |
delegate _n_test |
run | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 |
delegate _ref _cxx98 |
run | 通过 | 通过 | 通过 | 通过 | 失败 | 失败 | 通过 | 通过 |
delegate _ref _portable |
run | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 |
delegate _test |
run | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 |
delegate _test _fail1 |
compile _fail |
通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 |
delegate _test _fail2 |
compile _fail |
通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 |
lambda _test |
run | 通过 | 通过 | 通过 | 通过 | 失败 | 失败 | 通过 | 通过 |
mem_fun _cxx98 |
run | 通过 | 通过 | 通过 | 通过 | 失败 | 失败 | 通过 | 通过 |
mem_fun _portable |
run | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 |
stateless _test |
run | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 |
std_bind _cxx98 |
run | 通过 | 通过 | 通过 | 通过 | 失败 | 失败 | 通过 | 通过 |
std_bind _portable |
run | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 |
sum_avg _cxx98 |
run | 通过 | 通过 | 通过 | 通过 | 失败 | 失败 | 通过 | 通过 |
sum_avg _portable |
run | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 |
function _type |
run | 通过 | 通过 | 通过 | 通过 | 失败 | 通过 | 通过 | 通过 |
get _pointer |
run | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 |
multicast _and _empty |
run | 通过 | 通过 | 通过 | 通过 | 失败 | 通过 | 通过 | 通过 |
multicast _call _iterator |
run | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 |
multiple _inheritance |
run | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 |
解决 _select _cxx98 |
run | 通过 | 通过 | 通过 | 通过 | 失败 | 失败 | 通过 | 通过 |
解决 _select _portable |
run | 通过 | 通过 | 通过 | 通过 | 失败 | 失败 | 通过 | 通过 |
deletion _test |
run | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 |
signal _n_test |
run | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 |
signal_test | run | 通过 | 通过 | 通过 | 通过 | 失败 | 失败 | 通过 | 通过 |
type_info | run | 通过 | 通过 | 通过 | 通过 | 失败 | 通过 | 失败 (4) | 失败 (4) |
virtual _inheritance |
run | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 | 通过 |
(1) 在Intel C++编译器(WIN32)中,未指定的调用约定与__cdecl
完全相同。因此,请勿同时定义(未指定和__cdecl
)。
参考文献
- [Boost]。
Boost.Function
、Boost.Bind
、Boost.Mem_fn
和Boost.Signals
- [Sergey.FD]。Sergey Ryazanov的《The Impossibly Fast C++ Delegates》
- [Don.FD]。Don Clugston的《Member Function Pointers and the Fastest Possible C++ Delegates》
- [BIL]。Jonathan Turkanis和Christopher Diggins的《C++ Boost Interface Library (BIL)》
历史
- 2007年4月12日 - v1.00
- 首次发布。
- 2007年4月13日 - v1.01
- 文章:
Boost.Function
的any_pointer
出于相同原因不使用联合技巧。 - 文章:使用通用的函数指针
generic_fxn *
来标记函数,而不是void *
。 - 代码:使用预定义的(
typedef void generic_fxn();
)通用函数指针来标记函数,而不是void *
。已对target()
成员函数进行了此更改。
- 文章:
- 2007年4月18日 - v1.02
- 文章:不再支持为函数对象类型(FT08)的任意智能指针赋值。
- 文章:为VC6添加了使用
Boost 1.33.1
和Boost 1.34 alpha
的回归测试结果。 - 代码:为了帮助VC6进行重载解析,更改了应用
get_pointer()
的方式。不幸的是,结果是FT08智能指针类型不再可用。 - 代码:合并并修改了
Boost
的get_function_tag<>
以帮助VC6进行重载解析。 - 代码:为VC6添加了几个特定的解决方法。
- 代码:修改了几个测试示例为可移植语法,使其能在VC6中工作。
- 2007年4月30日 - v1.10
- 文章:有关联合技巧的不正确信息已被完全删除。
- 文章:添加了关于根据大小存储成员函数指针的新方法
any_fxn_pointer
的说明。 - 代码:移除了用于标记成员函数指针的错误联合技巧。
- 代码:添加了
any_fxn_pointer
实现以替代移除的联合技巧。 - 代码:添加了两个测试用例,用于检查将成员函数指针存储到多重继承类和
virtual
继承类。 - 代码:添加了
simplify_mfn
结构,类似于Don的SimplifyMemFunc
,以帮助MSVC,它无法将不相关类的成员函数指针进行转换,即使根据C++标准是必需的。
- 2007年6月1日 - 文章被编辑并移至CodeProject.com主文章库
(2)
gcc
支持调用约定,但无法有效解决它们之间的重载歧义。 (3)
FD.Delegate
基于Boost 1.34 pre alpha
的remove_xxx
类型特征构建,而不是Boost 1.33.1
,因为新版本包含了针对VC6、VC7和VC7.1的变通方法。 (4) VC71 & VC8 bug。 unds for VC6, VC7 and VC7.1 specific.
(4) VC71 & VC8 bug.
参考文献
- [Boost]。
Boost.Function
、Boost.Bind
、Boost.Mem_fn
和Boost.Signals
- [Sergey.FD]。Sergey Ryazanov的《The Impossibly Fast C++ Delegates》
- [Don.FD]。Don Clugston的《Member Function Pointers and the Fastest Possible C++ Delegates》
- [BIL]。Jonathan Turkanis和Christopher Diggins的《C++ Boost Interface Library (BIL)》
历史
- 2007年4月12日 - v1.00
- 首次发布。
- 2007年4月13日 - v1.01
- 文章:
Boost.Function
的any_pointer
出于相同原因不使用联合技巧。 - 文章:使用通用的函数指针
generic_fxn *
来标记函数,而不是void *
。 - 代码:使用预定义的(
typedef void generic_fxn();
)通用函数指针来标记函数,而不是void *
。已对target()
成员函数进行了此更改。
- 文章:
- 2007年4月18日 - v1.02
- 文章:不再支持为函数对象类型(FT08)的任意智能指针赋值。
- 文章:为VC6添加了使用
Boost 1.33.1
和Boost 1.34 alpha
的回归测试结果。 - 代码:为了帮助VC6进行重载解析,更改了应用
get_pointer()
的方式。不幸的是,结果是FT08智能指针类型不再可用。 - 代码:合并并修改了
Boost
的get_function_tag<>
以帮助VC6进行重载解析。 - 代码:为VC6添加了几个特定的解决方法。
- 代码:修改了几个测试示例为可移植语法,使其能在VC6中工作。
- 2007年4月30日 - v1.10
- 文章:有关联合技巧的不正确信息已被完全删除。
- 文章:添加了关于根据大小存储成员函数指针的新方法
any_fxn_pointer
的说明。 - 代码:移除了用于标记成员函数指针的错误联合技巧。
- 代码:添加了
any_fxn_pointer
实现以替代移除的联合技巧。 - 代码:添加了两个测试用例,用于检查将成员函数指针存储到多重继承类和
virtual
继承类。 - 代码:添加了
simplify_mfn
结构,类似于Don的SimplifyMemFunc
,以帮助MSVC,它无法将不相关类的成员函数指针进行转换,即使根据C++标准是必需的。
- 2007年6月1日 - 文章被编辑并移至CodeProject.com主文章库