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

Soyez le premier à donner votre avis sur cette source.

Snippet vu 6 886 fois - Téléchargée 34 fois

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

Ajouter un commentaire

Commentaires

xterminhate
Messages postés
371
Date d'inscription
dimanche 4 janvier 2004
Statut
Membre
Dernière intervention
23 septembre 2009

J'ai qd même ajouter une fonction membre "without_throw" avec un return par défaut qui effectivement ,'a que peu de sens.

Merci encore pour ton aide.
plus_plus_fab
Messages postés
232
Date d'inscription
vendredi 9 janvier 2004
Statut
Membre
Dernière intervention
8 janvier 2005

salut,

effectivement V1 n'apporte rien, et le pire (je me confesse), c'est que je n'en étais pas conscient !
La version 2 souffre d'un gros défaut : on ne peut pas appeler what() si l'exception ne dérive pas de std::exception. On perd donc le message d'erreur. De plus, si la classe d'exception possede d'autres informations accessibles avec d'autres méthodes que what(), c'est perdu aussi (qu'elle dérive de std::exception ou non). A abandonner donc.
Pour la version 3, ça me parait difficile de retourner qqchose de cohérent ... (il n'y a pas de return)
Finalement, je pense que ton code est bon tel qu'il est.
xterminhate
Messages postés
371
Date d'inscription
dimanche 4 janvier 2004
Statut
Membre
Dernière intervention
23 septembre 2009

Dans la V1, le try/catch n'apporte rien par rapport à l'original. La V2 peut effectivement répondre à un besoin spécifique : masquer toutes les exceptions (std ou pas).

Je proposerait égallement une V3 "bete&mechante" :
inline t_return call_wo_throw( t_argument parameter ) throw ()
{
try
{
return ( _class_instance->*_method )( parameter );
}
catch(...)
{
// rien
}
}
plus_plus_fab
Messages postés
232
Date d'inscription
vendredi 9 janvier 2004
Statut
Membre
Dernière intervention
8 janvier 2005

effectivement, ça devient pas mal je trouve ...
Je te propose 2 solutions pour propager les exceptions :

// v1 :
inline t_return call( t_argument parameter )
{
if( _class_instance == 0 )
throw bad_instance();
if( _method == 0 )
throw bad_method();
t_return tr;
try {
tr = ( _class_instance->*_method )( parameter );
}
catch(...)
{
throw; // l'appelant se débrouille
}
return tr;
}

// v2 :
on est sur que cette méthode ne déclenchera pas d'autres exceptions.
personellement, je ne définirais pas les classes d'exceptions à l'intérieur
des classes de call_back. Par contre, je les emballerai dans le meme
espace de nommage.
cette solution amene naturellemnt à faire hériter bad_instance, bad_method, bad_call_unexpected et bad_call, de bad_callback (par exemple), lui meme héritant de std::exception (comme c'est le cas).

inline t_return call( t_argument parameter ) throw (bad_instance, bad_method, bad_call, bad_call_unexpected)
{
if( _class_instance == 0 )
throw bad_instance();
if( _method == 0 )
throw bad_method();
t_return tr;
try {
tr = ( _class_instance->*_method )( parameter );
}
catch(const std::exception& e) // eventuellement !
{
throw bad_call(e.what());
}
catch(...)
{
throw bad_call_unexpected();
}
return tr;
}

Je n'ai pas de préférences :/, qu'en penses-tu ?
xterminhate
Messages postés
371
Date d'inscription
dimanche 4 janvier 2004
Statut
Membre
Dernière intervention
23 septembre 2009

Bonjour,

L'ecriture des classes callback reste perfectible. Mais cela prend une tournure intéressante ! Merci pour tes conseils. ^_^

Cordialement,
X.
Afficher les 17 commentaires

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.