Optimisation des callbacks c++ résolus à la compilation.

Contenu du snippet

Voici une classe de base pour réaliser simplement des callbacks "statiques" en C++. J'entends par "statiques", des callbacks pouvant être résolus au moment de la compilation.

La configuration de l'objet et de la fonction membre appelés est réalisée par template. Ainsi la mise en oeuvre du callback n'induit aucune surcharge à l'execution (à condition d'utiliser un compilateur/optimiseur performant).

Source / Exemple :


//////////////////////////////////////////////////////////////////////
// Functor callback :
//////////////////////////////////////////////////////////////////////

#include<exception>
using std::exception;

// Configuration :
// - du type d'objet appelé,
// - du type de la valeur de retour de la fonction membre appelée,
// - du type de l'argument de la fonction membre appelée,
// - du pointeur de fonction membre.

// Callback statique vers une fonction membre non const :
template<
typename t_class,
typename t_return,
typename t_argument,
t_return ( t_class::*fm )( t_argument )
>
struct t_callback
{
	t_callback( t_class& c ) : _callback_class( c )	{}
	inline t_return operator()( t_argument arg )
	{
		return (_callback_class.*fm)( arg );
	}
private:
	t_class& _callback_class;
};

// Callback statique vers une fonction membre const :
template<
typename t_class,
typename t_return,
typename t_argument,
t_return ( t_class::*fm )( t_argument ) const
>
struct t_callback_const
{
	t_callback_const( const t_class& c ) : _callback_class( c )	{}
	inline t_return operator()( t_argument arg ) const
	{
		return (_callback_class.*fm)( arg );
	}
private:
	const t_class& _callback_class;
};

// Callback "dynamique" vers une fonction membre non const :
template <
typename t_class,
typename t_return,
typename t_argument
>
struct t_callback_dyn
{
	// exception
	struct bad_instance : exception
	{
		const char* what() const throw() { return "bad instance"; }
	};
	// exception
	struct bad_method : exception 
	{ 
		const char* what() const throw() { return "bad method"; }
	};
	// type de fonction membre
    typedef t_return ( t_class::*t_method )( t_argument );
	// constructeur
	t_callback_dyn( t_class* class_instance = 0, t_method method = 0 ) :
	_class_instance( class_instance ),
	_method( method )
    {
		
    }
	void set( t_class* class_instance, t_method method )
	{
		_class_instance = class_instance;
		_method = method;
	}
    inline t_return operator()( t_argument parameter )
    {
		return ( _class_instance->*_method )( parameter );
    }
    inline t_return call( t_argument parameter )
    {
		if( _class_instance == 0 ) 
			throw bad_instance();
		if( _method == 0 )
			throw bad_method();
		return ( _class_instance->*_method )( parameter );
    }
    inline t_return call_wo_throw( t_argument parameter ) throw()
    {
		try
		{
			if( _class_instance != 0 && _method != 0 )
				return ( _class_instance->*_method )( parameter );
			else
				return t_return();
		}
		catch(...) { return t_return(); }
    }
private:
    t_class*  _class_instance;
    t_method  _method;

};

// Callback "dynamique" vers une fonction membre const :
template <
typename t_class,
typename t_return,
typename t_argument
>
struct t_callback_dyn_const
{
	// exception
	struct bad_instance : exception
	{
		const char* what() const throw() { return "bad instance"; }
	};
	// exception
	struct bad_method : exception 
	{ 
		const char* what() const throw() { return "bad method"; }
	};
	// type de fonction membre
    typedef t_return ( t_class::*t_method )( t_argument ) const;
	// constructeur
	t_callback_dyn_const( t_class* class_instance = 0, t_method method = 0 ) :
	_class_instance( class_instance ),
	_method( method )
    {
		
    }
	void set( t_class* class_instance, t_method method )
	{
		_class_instance = class_instance;
		_method = method;
	}
    inline t_return operator()( t_argument parameter ) const
    {
		return ( _class_instance->*_method )( parameter );
    }
    inline t_return call( t_argument parameter ) const
    {
		if( _class_instance == 0 ) 
			throw bad_instance();
		if( _method == 0 )
			throw bad_method();
		return ( _class_instance->*_method )( parameter );
    }
    inline t_return call_wo_throw( t_argument parameter ) const throw()
    {
		try
		{
			if( _class_instance != 0 && _method != 0 )
				return ( _class_instance->*_method )( parameter );
			else
				return t_return();
		}
		catch(...) { return t_return(); }
    }
private:
    t_class*  _class_instance;
    t_method  _method;

};

//////////////////////////////////////////////////////////////////////
// Mise en oeuvre et exemples
//////////////////////////////////////////////////////////////////////

// Variables de mesure des performances
const unsigned int nb_tests = 200;
const unsigned int nb_iterations = 200000000;

#include<vector>
#include<sstream>
#include<iostream>
#include<algorithm>
#include<functional>
#include<numeric>
#include<ctime>
using namespace std;

// Pour l'exemple, les fonctions membres appellées ont le prototype :
// "int f( const int& )" ou "int f( const int& ) const"

// Objet appelé n°1 pour l'exemple.
struct A
{
	A( const int& n ) : _coef( n ) {}
	int fa( const int& n ) { return _coef + n; }
private:
	int _coef;
};

// Objet appelé n°2 pour l'exemple.
struct B
{
	B( const int& n ) : _coef( n ) {}
	int fb( const int& n ) { return _coef + n; }
private:
	int _coef;
};

// Objet appelé n°3 pour l'exemple.
struct C
{
	C( const int& n ) : _coef( n ) {}
	int fc( const int& n ) const { return _coef + n; }
private:
	int _coef;
};

// Objet appelant sans callback (appel codé en dur) pour l'exemple.
struct W
{
	W( A& a ) : _a( a ) {}
	void event() { _a.fa( 888 ); }
private:
	A& _a;	
};

//////////////////////////////////////////////////////////////////////
//
// Si ce n'est pas A::fa mais B::fb qu'il faut appeler,
// la classe W doit être recoder en conséquence.
//
// Pour mettre en oeuvre un callback statique, il faut preciser au
// moment de la compilation (par template), le type de l'objet appelé
// et passer le pointeur vers la fonction membre appelée.
// A la construction de la classe intégrant un callback, il faut
// passer par référence une instance de l'objet appelé.
// A l'utilisation, le callback se manipule comme la fonction membre
// appelée (operateur ()).
//
//////////////////////////////////////////////////////////////////////

// Objet appelant avec callback "statique" (fonction membre non const)
template< typename t_class, int ( t_class::*fm )( const int& ) >
struct X
{
	X( t_class& c ) : callback( c ) {} 
	void event() { callback( 888 ); }
private:
	t_callback< t_class, int, const int&, fm > callback;
};

// Objet appelant avec callback "statique" (fonction membre const)
template< typename t_class, int ( t_class::*fm )( const int& ) const >
struct Y
{
	Y( t_class& c ) : callback( c ) {} 
	void event() { callback( 888 ); }
private:
	t_callback_const< t_class, int, const int&, fm > callback; // callback
};

// Objet appelant avec callback "dynamique" (fonction membre non const) 
// sans test class/methode
template< typename t_class >
struct Z
{
	typedef int ( t_class::*fm )( const int& );
	Z( t_class& c, fm f ) : callback( &c, f ) {} 
	void event() { callback( 888 ); }
private:
	t_callback_dyn< t_class, int, const int& > callback;
};

// Objet appelant avec callback "dynamique" (fonction membre non const)
// avec test class/methode
template< typename t_class >
struct Zt
{
	typedef int ( t_class::*fm )( const int& );
	Zt( t_class& c, fm f ) : callback( &c, f ) {} 
	void event() { callback.call( 888 ); }
private:
	t_callback_dyn< t_class, int, const int& > callback;
};

// Objet appelant avec callback "dynamique" (fonction membre const) 
// sans test class/methode
template< typename t_class >
struct Zc
{
	typedef int ( t_class::*fm )( const int& ) const;
	Zc( t_class& c, fm f ) : callback( &c, f ) {} 
	void event() { callback( 888 ); }
private:
	t_callback_dyn_const< t_class, int, const int& > callback;
};

// Objet appelant avec callback "dynamique" (fonction membre const) 
// sans test class/methode
template< typename t_class >
struct Ztc
{
	typedef int ( t_class::*fm )( const int& ) const;
	Ztc( t_class& c, fm f ) : callback( &c, f ) {} 
	void event() { callback.call( 888 ); }
private:
	t_callback_dyn_const< t_class, int, const int& > callback;
};

int main()
{
	// Varaibles de mesure des performances
	vector<clock_t> duree_ss_callback;
	vector<clock_t> duree_av_callback_sta;
	vector<clock_t> duree_av_callback_stac;
	vector<clock_t> duree_av_callback_dyn;
	vector<clock_t> duree_av_callback_dynt;
	vector<clock_t> duree_av_callback_dync;
	vector<clock_t> duree_av_callback_dyntc;
	
	// Objets appelés :
	A a( 12 );
	//B b( 12 );
	C c( 12 );

	// Boucle de test
	for( int repetition = nb_tests; repetition != 0; repetition-- )
	{
	
		// Test de performance avec un objet appelant sans callback :
		{
			W w( a );
			clock_t debut = clock();
			for( unsigned int iteration = nb_iterations; iteration !=0; iteration-- )
				w.event();
			duree_ss_callback.push_back( clock() - debut );
		}	
		// ... pas possible d'appeler une autre fonction membre de A, ou un autre objet sans recoder...
	
		// Test de performance avec un objet appelant avec callback statique (non const):
		{
			X<A, &A::fa> xa( a ); // Objet configuré pour appeler A::fa
			clock_t debut = clock();
			for( unsigned int iteration = nb_iterations; iteration !=0; iteration-- )
				xa.event();
			duree_av_callback_sta.push_back( clock() - debut );
		}	
	
		// Test de performance avec un objet appelant avec callback statique (non const):
		{
			Y<C, &C::fc> yc( c ); // Objet configuré pour appeler C::fc (methode const)
			clock_t debut = clock();
			for( unsigned int iteration = nb_iterations; iteration !=0; iteration-- )
				yc.event();
			duree_av_callback_stac.push_back( clock() - debut );
		}	
	
		// Test de performance avec un objet appelant avec callback dynamique (non const) (ss test class/methode):
		{
			Z<A> za( a, &A::fa ); // Objet configuré pour appeler A::fa
			clock_t debut = clock();
			for( unsigned int iteration = nb_iterations; iteration !=0; iteration-- )
				za.event();
			duree_av_callback_dyn.push_back( clock() - debut );
		}	

		// Test de performance avec un objet appelant avec callback dynamique (non const) (av test class/methode):
		{
			Zt<A> zta( a, &A::fa ); // Objet configuré pour appeler A::fa
			clock_t debut = clock();
			for( unsigned int iteration = nb_iterations; iteration !=0; iteration-- )
				zta.event();
			duree_av_callback_dynt.push_back( clock() - debut );
		}	

		// Test de performance avec un objet appelant avec callback dynamique (const) (ss test class/methode):
		{
			Zc<C> zc( c, &C::fc ); // Objet configuré pour appeler A::fa
			clock_t debut = clock();
			for( unsigned int iteration = nb_iterations; iteration !=0; iteration-- )
				zc.event();
			duree_av_callback_dync.push_back( clock() - debut );
		}	

		// Test de performance avec un objet appelant avec callback dynamique (const) (ac test class/methode):
		{
			Ztc<C> ztc( c, &C::fc ); // Objet configuré pour appeler A::fa
			clock_t debut = clock();
			for( unsigned int iteration = nb_iterations; iteration !=0; iteration-- )
				ztc.event();
			duree_av_callback_dyntc.push_back( clock() - debut );
		}	
	}
	
	// affichage des performances
	cout << "Temps d'execution sans callback                                : " << accumulate( duree_ss_callback.begin(), duree_ss_callback.end(), 0 ) << "ms" << endl;
	cout << "Temps d'execution avec callback statique (non const)           : " << accumulate( duree_av_callback_sta.begin(), duree_av_callback_sta.end(), 0 ) << "ms" << endl;
	cout << "Temps d'execution avec callback statique (const)               : " << accumulate( duree_av_callback_stac.begin(), duree_av_callback_stac.end(), 0 ) << "ms" << endl;
	cout << "Temps d'execution avec callback dynamique (non const)(ss test) : " << accumulate( duree_av_callback_dyn.begin(), duree_av_callback_dyn.end(), 0 ) << "ms" << endl;
	cout << "Temps d'execution avec callback dynamique (non const)(av test) : " << accumulate( duree_av_callback_dynt.begin(), duree_av_callback_dynt.end(), 0 ) << "ms" << endl;
	cout << "Temps d'execution avec callback dynamique (const)(ss test)     : " << accumulate( duree_av_callback_dync.begin(), duree_av_callback_dync.end(), 0 ) << "ms" << endl;
	cout << "Temps d'execution avec callback dynamique (const)(av test)     : " << accumulate( duree_av_callback_dyntc.begin(), duree_av_callback_dyntc.end(), 0 ) << "ms" << endl;
}

Conclusion :


Limitation :
- Ne compile pas sous VC6 (testé avec MinGW, Cygwin et Visual.Net-7.1).

Observation :
- Excellent niveau de performance avec Visual.Net-7.1 pour les callbacks statiques (inlining poussé),
- Optimisation peu convaincante avec GCC/win32 (Cygwin et MinGW).

A faire :
- Améliorer la génération et la propagation des exceptions dans toutes les classes callback.

Toutes remarques sont les bienvenues ! ;-)

A voir également

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.