注意,使用最新的boost需要进行修改:Just replaceboost::ct_if
withboost::mpl::if_c
(and#include
<boost/mpl/if.hpp>
) in Jae's Fast Delegate code.
Introduction
There have been several C++ delegates which declared themselves as a 'fast' or 'fastest' delegate,
whileBoost.Function
and its siblings,Boost.Bind
andBoost.Mem_fn
,
were adopted as part ofC++ Standards Committee'sLibrary Technical Report (TR1).
So, what are those called 'fast' or 'fastest' delegates and how much 'faster' are they
thanBoost.Function
?
The prefix 'fast' in the term 'fast(est) delegate' means either 'fast' invocation or 'fast' copy, or both. But, I believe, what is really an issue between the two when using the 'non-fast'Boost.Function
is
more likely its awful copy performance. This is due to the expensive heap memory allocation that is required to store the member function and the bound object on which member function call is made. So, 'fast' delegate often refers to a delegate that does not
require heap memory allocation for storing the member function and the bound object. In C++, as an object oriented programming paradigm, use of delegate or closure for the member function and the bound object is one of the most frequently occurring practices.
Thus, a 'fast' delegate can 'boost' the performance by far in some situations.
The following four graphs are the result of the invocation speed comparison among three fast delegates andBoost.Function
in
the various function call scenarios. See the '%FD_ROOT%/benchmark' for details.
- Don's Fastest Delegate
- Sergey's Fast Delegate
- Jae's Fast Delegate
Boost.Function
The following two graphs are the result of the copy speed comparison among three fast delegates andBoost.Function
.
For a bound member function call, it was found thatBoost.Function
can take 150 times longer than the fastest. The
result may vary based on the benchmark platform and environment, but it is obvious that the copy performance ofBoost.Function
is
not acceptable in certain cases.
In spite of the prominent speed boost of the fast delegates in specific cases, it is not comfortable for many programmers to switch and start using the fast delegates. This is because their features are not as
rich asthose which Boost.Function
and its siblings provide, and we are already accustomed to usingBoost
s.
These fast delegates support very limited types of callable entities to store and mostly do not support the storing of a function object, which is another frequently occurring practice in C++.
I had implemented a fast delegate some time ago, but it was not as fast as other fast delegates nor as C++ Standard compliant as I thought it was. I actually patched it to be C++ Standard compliant later. This
is the second version, but it is completely re-implemented from the scratch. The old version is obsolete. It is another 'fast' delegate, but it is also aBoost.Function
'drop-in'
replacement and more. I say 'more' because it supports the multicast feature which is missing in the most of C++ delegates currently available. It is not like an ancillary class to support multicast, but one class instance acts as single cast and multicast
on demand, without any runtime performance penalty.FD.Delegate
can be thought of as an aggregation ofBoost.Function
and
its siblings (Boost.Bind
andBoost.Mem_fn
)
plus some features fromBoost.Signals
. See the 'Delegates Comparison Chart' at the end of the article for particulars.
Using the code
As stated previously,FD.Delegate
is aBoost.Function
'drop-in'
replacement. So it is reasonable to refer to the online documentation ofBoost.Function
and especiallyBoost.Function
tutorialfor
features ofFD.Delegate
. Just make sure to add '%FD_ROOT%/include' as a system include directory.
- Example #1 fromBoost.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; }
- Example #2 fromBoost.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
supports multicast and uses C#'s multicast syntax,operator
+=
andoperator -=
.
#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; }
While a function pointer is equality comparable, a function object is not quite determinant at compile-time, whether equality comparable or not. This fact makesoperator
-=
pretty much useless for removing a function object from multicast.FD.Delegate
hasadd()
andremove()
member
function pairs to remedy the issue.add()
returns an instance offd::multicast::token
which
can be used to remove the added delegate(s).
#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; }
It has been one of the main concerns for a multicast delegate how to manage multiple return values.Combiner
interfaceofBoost.Signals
has been adopted, but has slightly different usage and syntax. The type of the combiner
interface is not a part ofFD.Delegate
type, although it is forBoost.Signals
,
as the form of the template parameter when declaring a signal variable. Instead,FD.Delegate
has a special function
call operator which takes the instance of the combiner interface as the last function call argument.
#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; }
Under the hood
Part A: storing a function pointer for later invocation without requiring heap memory allocation.
According to C++ standards, a function pointer -- both free function pointer and member function pointer -- cannot be converted or stored into avoid
*
. A function pointer may be converted into a function pointer of a different type signature, however, the result of such conversion cannot be used; it can only be converted back. The size of the member function varies over the different platforms from
4 bytes to 16 bytes. To avoid heap allocation to store the member function, some well-known template meta programming techniques have been adapted. These permit a member function pointer whose size is less than or equal to the size of the predefined generic
member function pointer to be stored without heap memory allocation. The stored generic member function pointer is restored back to its original member function type before use.
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_; };
A member function pointer whose size is less than or equal toany_fxn_size
is
stored intoany_fxn_pointer
.any_fxn_pointer
is
implemented to make it able to store one out of three different pointer types -- avoid
data pointer, a function pointer,
or a member function pointer -- whose size is less than a function pointer to the member of a multiple inherited class. Only one pointer type is stored at one specific time. Care has been taken regarding the misalignment issue, which may cause undefined behavior
according to the C++ standard, by applying the specialized version of the well-known max alignment union trickery.
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 ); }
When the size of a member function pointer is greater thanany_fxn_size
,
takes for an example when storing a member function pointer tovirtual
inherited class in MSVC, it is stored by allocating
heap memory in the same way thatBoost.Function
does, as a non-fast delegate. However, in real world practices,virtual
inheritance
is rarely used.
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, ); }
Part B: using any_fxn_pointer
any_fxn_pointer is used for storing an arbitrary function pointer with the class template. This is done in order to erase the type while storing the function and to restore the original type safely when required later. Sergey Ryazanov demonstrated inhis articlethat a C++ standard compliant fast delegate for member function can be implemented using the class template with a non-type member function template parameter. The sample below shows a rough idea of how it had been implemented.
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 ); } };
Even though passing a member function as a non-type template parameter is a legitimate C++ feature -- somewhat outdated, but still widely used -- compilers do not support it. The real problem with Sergey's implementation of using the member function as non-type template parameter is not the lack of support from those outdated compilers, but rather its awful syntax. This is due to the fact that non-type template parameters cannot participate in template argument deduction from the function arguments provided. Therefore it must be explicitly specified all the time.
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 ); }
It is a really bad idea in practice that you should provide template arguments explicitly every time. Usingany_fxn_pointer
introduced
previously, we can significantly improve the syntax of the usage and, in turn, the degree of convenience. We just need to add anany_fxn_pointer
as
a member of the delegate class to store the address of the function of interest.
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
Not even this works for those outdated compilers that do not support member functions as non-type template parameters. It becomes more intuitive syntax and thus easier to use, since function overloading and automatic template argument deduction is now applicable.
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 ); }
Part C: using function reference tables to support rich features
One of interesting features ofBoost.Function
is its ability to store
a member function along with the bound object in various forms: a) a pointer or a reference to the bound object of the type on which the member function call is made, or b) an instance of an arbitrary smart pointer to the bound object. As a matter of fact,
it is precise to say that this feature is ofBoost.Mem_fn
andBoost.Bind
.Boost.Function
has
a 'manager
' member and defines enumerate tags of so-called 'functor_manager_operation_type
'.
enum functor_manager_operation_type
{
clone_functor_tag,
destroy_functor_tag,
check_functor_type_tag
};
It also defines several tags to distinguish among different types of functions.
struct function_ptr_tag {}; struct function_obj_tag {}; struct member_ptr_tag {}; struct function_obj_ref_tag {}; struct stateless_function_obj_tag {};
This is a traditional tag dispatching technique that uses function overloading to dispatch based on properties of type. This sort of tag dispatching works quite reasonably, but with slightly over complicated construct
detail. This is because an implementation for a specific function type is usually scattered here and there. If you have ever tried to look into theBoost.Function
detail,
you know what I mean. However, there exists one very neat and elegant alternative. It was initially introduced by Chris Diggings and Jonathan Turkanis in their series of articles aboutBIL
(Boost.Interface.Libarary). We can start off by declaring a function reference table that contains a set of function pointers. These function pointers determine the behavioral requirement or concepts of the delegate that we want to design.
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_;
Then we define class templates that implement all of the entries of function reference table respectively.
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; } };
We have defined three class templates above. Actually, a class template is not necessary if no extra template parameter is required to implement the behavioral requirement of the specific function type. A normal class will be just fine in such case.
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; } };We can now implement delegate class. We only use entries of the function reference table we declared in the first place through the pointer to the function reference table,
tbl_ptr_
,
to implement member functions of a 'generic' delegate itself.
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 ); }Defining a class to represent 'null' has several benefits over initializing the pointer to the function reference table,
tbl_ptr_
,
to a zero value. It is not necessary to check nullness of delegate at runtime. If the pointer to the function reference table were to be assigned to zero to represent null delegate instead, the function call operator should have contained an if-clause to check
the nullness. Most of all, it should also have had an exception statement to throw when the call had been made on the empty delegate, which seemed quite an unreasonable penalty.
Since nullness of delegate is determined at compile time by introducing 'null' class,null_stub
,
we can improve the performance by not checking the nullness at runtime and by not having throw statement if it is not a 'null' delegate. Finally, we can complete our new delegate class by adding interface member functions to support storing from various function
types.
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
We just added smart pointer support into our delegate. We can now use any kind of smart pointer to bind the target object on which the member function call is made, as long as theget_pointer()
overload
for the smart pointer is provided in the visible namespace scope.
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 ); }
As demonstrated above, it is quite simple to add new function type support into our delegate. First, it determines all the behavioral requirements according to the characteristic of the function type that we are
interested in storing. Then it adds a class template and starts defining all the entries of function reference table for the new function type per requirements. Finally, it adds an interface function overload --from_function()
member
function in the example above -- to support the newly added function type.
If we want to add more behavioral requirements or concepts into the delegate, we can add new entries into the function reference table to fulfill the purpose. Of course, all of the existing class templates already defined for some other function types must implement newly added entries accordingly. Otherwise, it will assert a compile error. Be aware that even if these newly added entries might not apply for some of class templates already defined for certain function types, they still have to define empty entries, which may do nothing and thus possibly be optimized away by the compiler.
As easily seen from the example above, all entries in a function reference table are declared in a type neutral form. This means that it may be determined by the function call type signature, but nothing else. Also, we can see that void pointers are passed over as their arguments, and the specific type information is given as the template parameters of the class template that implements all of the entries of the function reference table. Those void pointers passed in and out come alive and restore themselves back to their original identities by the aid of casting to one of the template parameters of class template. However, it will assuredly erase the type information only during its storage life span and convert back to the right identity when required. This is the key point of how the different set of behavioral requirements of a delegate for a specific function type is implemented in the type-safe manner, but with type-neutral persistency.
Implementation details are to be defined in one location, in a class template for a specific function type, so it has better readability and manageability than the tag dispatching technique. It is like a 'strategy pattern' to make it possible to encapsulate the interchangeable algorithm of delegate for various function types. Actually, a function reference table is an interface and the technique can be thought of as a static typing or static binding version of the virtual table feature in C++.
Every individual class template that implements all of the entries of a function reference table according to their own requirements for the specific function type are orthogonal to each other. This means that we don't need to concern ourselves with how a function object is stored into the delegate while designing a class template to store a member function, and vice versa. So what does this mean? I can now implement and support a multicast delegate much easier in one single delegate class definition. There will be absolutely no degradation in runtime performance nor will there be a size increase of non-multicast delegate. This is because they -- that is, the multicast delegate and non-multicast delegate -- are treated as completely different beasts of the same name.
Therefore it becomes very important how robustly we declare entries of a function reference table to generalize the behavioral requirements of the generic delegate that we are interested in storing from the given function type, as well as how to categorize these function types based on their own unique traits and requirements.
Selecting a proper function reference table for the specific function type takes place in the implementation of the interface function overloads of delegate class. It usually involves template meta programming
along with type traits to determine or to transform from a function type. Unfortunately, some type traits I used while implementing the selection structure in the interface function overloads only work in places based on the template partial specialization
feature of C++ (boost::remove_pointer<>
for example). This is whyFD.Delegate
does
not work on some outdated compilers that have no support or broken support for this feature. If I were to support only a limited feature set, like Don's fastest delegate or Sergey's fast delegate, I could have supported many outdated compilers as well. However,
I wanted it to be a 'drop-in' replacement forBoost.Function
, so this was not an option for me.
D. Categorizing function types.
There are three dominant callable entities in the C++ world.
- Free function
- Member function
- Function object
These basic callable entities are strained off more refined species depending on how their bound object or function object is stored and managed internally. See the table below.
1. | Free function (including static member function) |
2-a. | Member function on a pointer to an object of the type that the member function belongs to. |
2-b. | Member function on a reference to an object of the type that the member function belongs to. |
2-c. |
Member function on a copy of an object of either the type that the member function belongs to or any type that supportsget_pointer() overload
(i.e. smart pointers). |
2-d. | Member function bound with a pointer or a reference to an object of the type that the member function belongs to. |
2-e. |
Member function bound with a copy of an object of any type that supportsget_pointer() overload (i.e. smart pointers). |
3-a. | A pointer or a reference to a function object. |
3-b. | A copy of a function object. |
3-c. | Stateless function object. |
Empty delegate. | |
Multicast delegate. |
In the previous delegate example, we added supports for function type FT01, FT05, FT06 and FT10. To make it easier to understand what these function types are, see the example as illustrated below.
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 ); }
Part E: list of entries of function reference table
As emphasized previously, it is very important to declare common and representative entries of the function reference table for a generic delegate. It is the basis of the wholeFD.Delegate
's
design and determines performance, as well as robustness. The following list of function entries are declared in order to generalize the common behavioral requirements of a generic delegate for the various function types that we categorized above.
Invocation
- invoke() - Invokes the underlying callable entity.
Object management
- copy_obj() - Copies the object to destination from source.
- delete_obj() - Deletes the object.
General information inquiry
- is_empty() - Determines whether or not the delegate is empty.
- size_of_fxn() - Retrieves size of the underlying callable entity.
- type_of_fxn() - Retrieves std::type_info of the underlying callable entity.
- is_functor() - Determines whether or not the underlying callable entity is a function object.
Comparisons
- compare_equal_same_type() - Compares equality of two underlying callable entities of the same type.
- memcmp_delegate() - Non contextual memory comparison between underlying callable entities of two arbitrary types.
Multicast
- is_multicast() - Determines whether or not the delegate is a multicast.
- add_delegates() - Adds one or more delegates into multicast.
- remove_delegates() - Removes one or more delegates from multicast.
- find_delegate() - Determines whether or not the specified delegate is in multicast.
Part F: generic data structure to store a callable entity
It is required to have at least two void pointers and anany_fxn_pointer
to
store all the previously categorized function types into a generic delegate. To make it easy to access and manipulate these void pointers, as well asany_fxn_pointer
,
they are bundled up together into a single structure called 'delegate_holder
'.
struct delegate_holder { void const * obj_ptr; any_fxn_pointer any; void const * tbl_ptr; };
See the table below to understand how these void pointer members are used effectively in a situation of storing one of the various function types.
Part G: supporting multicast
In order to support multicast, it is important to model theEqualityComparableconcept
to be able to compare between two delegates of the same type. As explained in theFrequently Asked QuestionsofBoost.Function
,
it is a well-known fact that there is no reasonable method at the moment to distinguish whether or not the specific function object has an accessible equality comparison operator defined. Because of theBoost.Function
andFD.Delegate
erasing
of type information and restoring it back when required, it will assert a compile error. This is regardless of whether the comparison between two delegates of an arbitrary function object type has actually been performed or not. This is also the case if it
is forced to use the equality comparison operator when the equality comparison operator is not available or not accessible for the specified function object type.
Obviously, it is not acceptable to cause a compile error when we do not compare delegates. So,FD.Delegate
was
made to returnfalse
by default and it does not use the equality comparison operator when comparing between two delegates
of the same type that store a function object as their underlying callable entity. However, it can be altered to return the result of the equality comparison operator of the specific function object type, thus possiblytrue
,
if a certain condition is met. This will be explained later. By the way, there is no issue of comparing between two delegates of the same type when their underlying callable entity is either a free function or a member function, since function pointers of
the same type are equality comparable in C++.
As mentioned previously, it was possible to compare between two delegates of the same function object type using the equality comparison operator, instead of returningfalse
blindly,
if a certain condition is met. The user can manually indicate that the specific function object type has an accessible equality comparison operator by defining a template specialization offd::is_equality_comparable<>
for
the function object type or possibly by using the easier macro definition provided for this purpose,FD_DELEGATE_EQUALITY_COMPARABLE_TYPE
.FD.Delegate
will
use the equality comparison operator and will return the result of the comparison according to the implementation of the operator.
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) }
If line (A) is commented out, the assert statement (B) will always holdfalse
and
never becometrue
, as explained previously. Also note that we do not use the equality comparison operator forFD.Delegate
to
compare between the two. This is because it is not a valid expression inBoost.Function
for same reasons as explained
above. Furthermore,FD.Delegate
is aBoost.Function
'drop-in'
replacement. So instead, we use theequal_to()
member function for an equality comparison between two delegates. The
following code snippet is a pseudo-code illustration of how comparison between two delegates is performed in order.
// 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()
is one of entries of the function reference
table explained previously. So, a class template for a different function type can implement it differently according to its own requirement. All class templates whose designated function type requires the equality comparison between two of the same function
object type will implement such a comparison to returnfalse
. Otherwise,fd::is_equality_comparable<>
specialization
for the specified function object type is defined to indicate the availability of the equality comparison operator.
Unfortunately, even such limited support for the equality comparison is not applicable for comparing between two delegates of the same type for an anonymous function object that we frequently face in everyday STL
programming. It is not impossible, but it is impractical to definefd::is_equality_comparable<>
specialization for
an anonymous function object.Boost.Signals
solves this problem by providing theconnect()
method
to return an object called a 'connection
'. The 'connection
'
object can be used either to disconnect the connection made or to check the validity of the connection. The same idea goes withFD.Delegate
,
but with a slightly different name tag.FD.Delegate
has a member function calledadd()
,
which is almost equivalent tooperator +=
, but they are different in their return type.
operator +=
andoperator
-=
return a self-reference, whileadd()
returns an object called a 'token
'
andremove()
returns the number of delegates in the multicast delegate. 'token
'
is very similar to what is 'connection
' forBoost.Signals
.
It can be used either to remove one or more delegates added before or to check the validity of the 'token
' itself.
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 ); }
Part H: combiner interface
What is missing in C# delegate is multiple return values management of multicast invocation.Boost.Signals
employes
a technique, called combiner interface, to serve two purposes effectively: a) multiple return values management; b) multiple invocations control. See theBoost.Signals
documentation
for thecombiner interface.
The idea and most of the implementation details ofBoost.Signals
's combiner
interface are copied and incorporated intoFD.Delegate
. However, there is one big difference in their usage as multicast
delegates. In order to be coincident withBoost.Function
, the combiner interface ofFD.Delegate
is
not designed to be a part of the delegate class type as a form of template parameter the way it is forBoost.Signals
.
Instead,FD.Delegate
introduces a special function call operator that takes an instance of the combiner interface as
the last function call argument. See the example below.
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 ); }
While a combiner interface is used to manage multiple return values of multicast invocation in most cases, it can be also used to control the multiple invocation itself viamulticast_call_iterator
,
which is copied and modified fromBoost.Signals
'sslot_call_iterator
.
See the example below. It is possible to abort all invocations of delegates in the multicast or to skip certain invocations of delegates when the combiner interface provided is implemented to require such operational conditions.
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; }
Any combiner interface that is implemented forBoost.Signals
will work
forFD.Delegate
without a modification. It is obvious thatFD.Delegate
supports
many useful features ofBoost.Signals
when it operates as a multicast delegate. However, it is never meant to replaceBoost.Signals
.
Some very sophisticated features ofBoost.Signals
are not implemented inFD.Delegate
,
but it can still serve as more than a generic multicast delegate while it stands for a 'drop-in' replacement ofBoost.Function
.
Delegates comparison chart
Yes | Yes | Yes | Yes | Yes |
No | No | Yes | Yes | Yes |
No | No | Yes | Yes | Yes |
No | No | Yes | Yes | Yes |
Yes | Yes | Yes | Yes | Yes |
No | No | Yes | Yes | Yes |
No | No | Yes | Yes | Yes |
No | No | Yes | Yes | Yes |
No | No | Yes | Yes | Yes |
Possible | Possible | Yes | Yes | N/A |
No | No | Yes | No | Yes |
N/A | N/A | Yes | N/A | Yes |
N/A | N/A | Yes | N/A | Yes |
N/A | N/A | Yes | N/A | Yes |
N/A | N/A | No | N/A | Yes |
N/A | N/A | No | N/A | Yes |
N/A | N/A | No | N/A | Yes |
No | No | Yes | Yes | Yes |
Yes | Yes | Yes | No | No |
8 or 12 bytes | 8 bytes | 16 bytes + α | 12 bytes +α | 36 bytes +α |
8 or 12 bytes | 8 bytes | 16 bytes | 12 bytes +α | 36 bytes +α |
●●●●● | ●●●●● | ●●●●○ | ●○○○○ | ●○○○○ |
●●●●● | ●●●●○ | ●●●●○ | ●●●○○ | ●●●○○ |
Yes | No | Support | No | No |
No | No | Yes | Yes | No |
Yes | No | Yes | No | No |
N/A | N/A | Yes | Yes | Yes |
No | No | Yes | Yes | Yes |
No | No | Yes | Yes | No |
No | Yes | Yes | Yes | Yes |
●●●●● | ●●○○○ | ●●●●○ | ●●●●● | ●●●●● |
No | No | Yes | Yes | Yes |
Helper function template and class template
FD.Bind
FD.Bind
is a set of helper function overloads that return an instance
ofFD.Delegate
. UnlikeBoost.Bind
,FD.Bind
can
only be used to bind the object on which the member function call is made with the member function.FD.Bind
can return
aFD.Delegate
instance of either function type FT05 or FT06, where FT05 is a fast delegate function type. Of course,FD.Delegate
will
work withBoost.Bind
flawlessly as well.
#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
When support for the relaxed function type signature is implemented, the library become more useful. However, one issue has emerged as the price of convenience. See the example below.
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). }
Many libraries fromBoost
as well asFD.Delegate
suffer
from the ambiguity issue illustrated above. They usually support minimal but incomplete or inconvenient devices to remedy the issue. You can explicitly specify the return or argument type to help overload resolution by designating those types in the function
call syntax.
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
is a tiny utility class template that comes in handy in
such situations to resolve overload resolution ambiguity. It is much more generic and intuitive because you don't need to look into the individual library details to figure out the order of the template parameters in order to specify the argument types in
the right placement. Notice thatBoost.Bind
andBoost.Mem_fn
only
allow you to specify the return type to help function overload resolution. Also note thatFD.Resolution
is an independent
utility class and thus can be used withoutFD.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) }
Regression test
See the '%FD_ROOT%/libs/delegate/test' for details.FD.Delegate
has
been built based onBoost 1.33.1
. As mentioned,FD.Delegate
uses
several type trait classes fromBoost
to transform type information. These features, especiallyremove_xxx
s,
require compiler support for the partial template specialization. VC6 and VC7 do not support or have broken support for the partial template specialization. However, there are some.
run | Fail(1) | Fail(1) | Fail(2) | Fail(2) | Fail | Pass | Pass | Pass |
run | Pass | Pass | Pass | Pass | Fail | Pass | Pass | Pass |
run | Pass | Pass | Fail(2) | Fail(2) | Fail | Pass | Pass | Pass |
run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
run | Pass | Pass | Fail(2) | Fail(2) | Fail | Pass | Pass | Pass |
run | Fail(1) | Fail(1) | Fail(2) | Fail(2) | Pass | Pass | Pass | Pass |
run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
run | Pass | Pass | Fail(2) | Fail(2) | Pass | Pass | Pass | Pass |
run | Pass | Pass | Fail(2) | Fail(2) | Pass | Pass | Pass | Pass |
run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
run | Pass | Pass | Pass | Pass | Fail | Fail | Pass | Pass |
run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
run | Pass | Pass | Pass | Pass | Fail | Fail | Pass | Pass |
compile | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
run | Pass | Pass | Pass | Pass | Fail | Fail | Pass | Pass |
run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
run | Pass | Pass | Pass | Pass | Fail | Fail | Pass | Pass |
run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
compile _fail |
Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
compile _fail |
Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
run | Pass | Pass | Pass | Pass | Fail | Fail | Pass | Pass |
run | Pass | Pass | Pass | Pass | Fail | Fail | Pass | Pass |
run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
run | Pass | Pass | Pass | Pass | Fail | Fail | Pass | Pass |
run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
run | Pass | Pass | Pass | Pass | Fail | Fail | Pass | Pass |
run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
run | Pass | Pass | Pass | Pass | Fail | Pass | Pass | Pass |
run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
run | Pass | Pass | Pass | Pass | Fail | Pass | Pass | Pass |
run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
run | Pass | Pass | Pass | Pass | Fail | Fail | Pass | Pass |
run | Pass | Pass | Pass | Pass | Fail | Fail | Pass | Pass |
run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
run | Pass | Pass | Pass | Pass | Fail | Fail | Pass | Pass |
run | Pass | Pass | Pass | Pass | Fail | Pass | Fail(4) | Fail(4) |
run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
(1) Non-specified calling convention is treated exactly the same as__cdecl
in
Intel C++ compiler for WIN32. Therefore do not define both (non-specified and__cdecl
) at the same time.
References
-
[Boost].
Boost.Function
,Boost.Bind
,Boost.Mem_fn
andBoost.Signals
- [Sergey.FD]. "The Impossibly Fast C++ Delegates" by Sergey Ryazanov
- [Don.FD]. "Member Function Pointers and the Fastest Possible C++ Delegates" by Don Clugston.
- [BIL]. "C++ Boost Interface Library ( BIL )" by by Jonathan Turkanis and Christopher Diggins.
History
- April 12 2007 - v1.00
- Initial Release.
- April 13 2007 - v1.01
- ARTICLE:
Boost.Function
'sany_pointer
does not use the union trickery for the same reason. - ARTICLE: Uses a generic function pointer,
generic_fxn *
, to mark functions instead ofvoid *
. - CODE: Used the pre-defined (
typedef void generic_fxn();
) generic function pointer to mark functions intead ofvoid *
. This change has been made fortarget()
member function.
- ARTICLE:
- April 18 2007 - v1.02
- ARTICLE: Assigning an arbitrary smart pointer of function object type (FT08) is not supported anymore.
- ARTICLE: Added regression test results for VC6 with
Boost 1.33.1
andBoost 1.34 alpha
. - CODE: Changed the way to apply
get_pointer()
to help VC6 for the overload resolution. Unfortunately, as a result, FT08 smart pointer type can not be used anymore. - CODE: Incorporated and modified
Boost
'sget_function_tag<>
to help VC6 for the overload resolution. - CODE: Added Several workarouns for VC6 specific.
- CODE: Revised several test samples into portable syntax to make them work for VC6.
- April 30 2007 - v1.10
- ARTICLE: Incorrect information regarding to the union trickery has been completely removed.
- ARTICLE: Added explanation about the new approach to store member function pointer depending on its size.
any_fxn_pointer
. - CODE: Removed incorrect union trickery that was used for marking member function pointer.
- CODE: Added
any_fxn_pointer
implementation to substitute the removed union trickery. - CODE: Added two test cases to check storing member function pointer to multiple inherited class and
virtual
inherited class. - CODE: Added
simplify_mfn
struct simliar to Don'sSimplifyMemFunc
to help MSVC which can not cast a member function pointer of unrelated classes even though it is required according to C++ standards.
- June 1, 2007 - Article edited and moved to the main CodeProject.com article base
(2)
gcc
supports calling conventions, but does not effectively
resolve the overload ambiguity among them.(3)
FD.Delegate
has been built based onremove_xxx
type
traitsfromBoost 1.34 pre alpha
instead ofBoost
1.33.1
since the new version incorporatesworkaroundsfor VC6, VC7 and VC7.1 specific.(4) VC71 & VC8bug.unds for VC6, VC7 and VC7.1 specific.
(4) VC71 & VC8bug.
References
-
[Boost].
Boost.Function
,Boost.Bind
,Boost.Mem_fn
andBoost.Signals
- [Sergey.FD]. "The Impossibly Fast C++ Delegates" by Sergey Ryazanov
- [Don.FD]. "Member Function Pointers and the Fastest Possible C++ Delegates" by Don Clugston.
- [BIL]. "C++ Boost Interface Library ( BIL )" by by Jonathan Turkanis and Christopher Diggins.
History
- April 12 2007 - v1.00
- Initial Release.
- April 13 2007 - v1.01
- ARTICLE:
Boost.Function
'sany_pointer
does not use the union trickery for the same reason. - ARTICLE: Uses a generic function pointer,
generic_fxn *
, to mark functions instead ofvoid *
. - CODE: Used the pre-defined (
typedef void generic_fxn();
) generic function pointer to mark functions intead ofvoid *
. This change has been made fortarget()
member function.
- ARTICLE:
- April 18 2007 - v1.02
- ARTICLE: Assigning an arbitrary smart pointer of function object type (FT08) is not supported anymore.
- ARTICLE: Added regression test results for VC6 with
Boost 1.33.1
andBoost 1.34 alpha
. - CODE: Changed the way to apply
get_pointer()
to help VC6 for the overload resolution. Unfortunately, as a result, FT08 smart pointer type can not be used anymore. - CODE: Incorporated and modified
Boost
'sget_function_tag<>
to help VC6 for the overload resolution. - CODE: Added Several workarouns for VC6 specific.
- CODE: Revised several test samples into portable syntax to make them work for VC6.
- April 30 2007 - v1.10
- ARTICLE: Incorrect information regarding to the union trickery has been completely removed.
- ARTICLE: Added explanation about the new approach to store member function pointer depending on its size.
any_fxn_pointer
. - CODE: Removed incorrect union trickery that was used for marking member function pointer.
- CODE: Added
any_fxn_pointer
implementation to substitute the removed union trickery. - CODE: Added two test cases to check storing member function pointer to multiple inherited class and
virtual
inherited class. - CODE: Added
simplify_mfn
struct simliar to Don'sSimplifyMemFunc
to help MSVC which can not cast a member function pointer of unrelated classes even though it is required according to C++ standards.
- June 1, 2007 - Article edited and moved to the main CodeProject.com article base
相关推荐
# Users and groups runAsUser: rule: RunAsAny supplementalGroups: rule: RunAsAny fsGroup: rule: RunAsAny # Privilege Escalation allowPrivilegeEscalation: false defaultAllowPrivilegeEscalation...
SAF-Object-Delegate 基于 Kotlin 的委托机制实现对 Extra、SharedPreferences 的封装 模块 extras-delegate prefs-delegate prefs-fastjson-delegate prefs-gson-delegate 最新版本 一. 封装...
Delegate.Event.ThreadDelegate.EvDelegate.Event.Threadent.Thread
资源来自pypi官网。 资源全名:spyne-delegate-1.0.0.tar.gz
- NEW: UIWrapContent now has a settable delegate to initialize items, and will call it on Start(). - NEW: Added OnDragStarted to the scroll view for those that needed it. - NEW: Added the missing ...
02.C# 知识回顾 - 委托 delegate续.pdf02.C# 知识回顾 - 委托 delegate续.pdf
Delegate类、Invoke方法、Combine方法是哪来的呢? (二)、委托原理 1.delegate 关键字 (1).概念:delegate 关键字用于声明一个引用类型,该引用类型可用于封装命名方法或匿名方法。 (2)编译后生成的的中间代码。 ...
// C# LINQ - delegate Enumerable . Range ( 1 , 10 ) . Where ( delegate ( int i ) { return i % 3 == 0 ; } ) . Select ( delegate ( int i ) { return i * 10 ; } ) ; // linq.js - anonymous function ...
* In the event of an error, the socket is closed. * You may call "unreadData" during this call-back to get the last bit of data off the socket. * When connecting, this delegate method may be called...
delegate_execute.exe
Delegate:HotkeyHandler--用于热键响应的委托,其中传入参数为一个Hotkey对象和一个Message对 象,避免了Hotkey与Message的冲突,同时省去了Hotkey的频繁注册与注销的麻烦! Enum:KeyModifiers--热键注册时使用到的...
网上有很多关于C++ delegate机制的文章,但都讲的是函数指针的内容,上周就C++中实现C#的delegate机制的问题研究了好几天,查看了很多相关资料,才解决了问题,现将我写的C++ delegate测试程序贴出来,希望能帮到有...
01.C# 知识回顾 - 委托 delegate.pdf 01.C# 知识回顾 - 委托 delegate.pdf
20-indexer(1).swf 21-indexer(2).swf 22-indexer(3).swf 23-indexer(4).swf 24-delegate(1).swf 25-delegate(2).swf 26-event(1).swf 27-event(2).swf
SceneDelegate 在Xcode11.0以后创建项目,运行项目出现bug时遇到的问题之一,我的解决方法。 报错: -[AppDelegate setWindow:]: unrecognized selector sent to instance 0x60000002b440 修改方法: 在AppDelegate...
- FIX: UICenterOnClick shouldn't cache the panel anymore, making it work properly with drag & drop. - FIX: Widget inspector's Dimensions field should no longer be grayed out if the widget is partially...
swing框架之uidelegate之一.doc
支持使用typesafe-config...jar ...-fat.jar`SLF4J-记录我更喜欢使用SLF4j,因此,如果要启用它,请添加额外的系统属性 -Dvertx.logger-delegate-factory-class-name=io.vertx.core.logging.SLF4JLogDelegateFactory
Delegate.Office365.Common.WorkflowActions提供了一系列自定义工作流操作来补充 SharePoint Online 和 SharePoint 2013 上的现有操作,可作为 WSP 包部署到任何 SP.Web。 文档 网页服务 调用 OData Web 服务 自动...
Double-Pull-Delegate use Scroller and Delegate Gradle Add it in your root build.gradle at the end of repositories: allprojects { repositories { ... maven { url 'https://jitpack.io' } } } Add ...