OVH Cloud OVH Cloud

Definition d'une interface (a la Java) a l'aide de templates

62 réponses
Avatar
Aurélien REGAT-BARREL
Bonjour à tous,
Je voudrais traduire une interface au sens pur, vraiment logique (ensemble
de méthodes). Je ne veux pas passer par l'héritage et les classes abstraites
de bases, car notamment je ne veux pas de polymorphisme. Je me suis donc
rabatu sur les templates.
Le but est de traduire un shéma UML où divers objets implémentent une
interface ITriplet qui stipule que :
- l'objet est construit à partir de 3 valeurs A, B et C (=> un triplet)
- l'objet possède une méthode GetValue(int) permettant d'accéder à ces 3
valeurs
- l'objet possède une méthode statique GetTripletName() qui retourne le nom
du triplet

Le truc étant que la plupart des objets font des modifications sur A, B et C
avant de les stocker. Les divers objets qui implémentent ITriplet n'ont rien
en commun et ne doivent pas être comparables, pas mélangeables, ... d'où pas
de polymorphisme.

Voilà les solutions auxquelles j'ai pensé, quel est votre opinion :

Pour l'exemple, un seul objet ABC inmplémente ITriplet.

1- on laisse le programmeur faire tout ce qu'il faut (on le guide au
maximum)

#define INTERFACE(x) /**/
#define IMPLEMENTS(x) /**/
#define WHERE(x) /**/

INTERFACE( ITriplet
{
public:
ITriplet( double A, double B, double C );
double GetValue( int Index ) const;
static std::string GetTripletName();
};
)

// triplet ABC
class ABC IMPLEMENTS( ITriplet )
{
public:
ABC( double A, double B, double C )
{
data.reserve( 3 );
data.push_back( A );
data.push_back( B );
data.push_back( C );
}
double GetValue( int Index ) const
{
return this->data.at( Index );
}
static std::string GetTripletName()
{
return "A.B.C";
}
private:
std::vector<double> data;
};

template <typename T>
WHERE( T : ITriplet )
T TripletBuilder()
{
return T( 0, 1, 2 );
}

int main()
{
ABC abc = TripletBuilder<ABC>();
}

2- on a bien une interface ITriplet sous forme de template. C'est plus
l'aspect macro qui est exploité ici

// functor pour construire le triplet
typedef std::vector<double> (TripletCtor)( double A, double B, double C );
// functor pour récupérer son nom
typedef std::string (TripletGetName)();

// interface ITriplet
template<TripletCtor Func, TripletGetName Name>
class ITriplet
{
public:
ITriplet( double A, double B, double C ) :
data( Func( A, B, C ) )
{
}
double GetValue( int Index ) const
{
return this->data.at( Index );
}
static std::string GetTripletName()
{
return Name();
}
private:
std::vector<double> data;
};

// triplet ABC
std::vector<double> ComputeABC( double A, double B, double C )
{
std::vector<double> result;
result.reserve( 3 );
result.push_back( A );
result.push_back( B );
result.push_back( C );
return result;
}
std::string GetNameABC()
{
return "A.B.C";
}

typedef ITriplet<ComputeABC, GetNameABC> ABC;

3 - fournir un template de vérification des méthodes attendues. Au lieu de
donner une interface au programmeur comme en 1, on donne un test à faire
passer à son objet.

template<typename T>
class ITripletImplementationChecker
{
public:
ITripletImplementationChecker()
{
double a = 0.0;
double b = 0.0;
double c = 0.0;
T t( a, b, c );
t.GetValue( 0 );
t.GetValue( 1 );
t.GetValue( 2 );
T::GetTripletName();
}
};

ITripletImplementationChecker<ABC> check_abc;


Comme j'ai l'impression de faire n'importe quoi, j'aimerais avoir votre
avis.
Merci de votre aide.

--
Aurélien REGAT-BARREL

10 réponses

1 2 3 4 5
Avatar
Franck Branjonneau
"Aurélien REGAT-BARREL" écrivait:

Ohhhhhh...
On peut faire ça en C++...


Et pire encore :

template <class T> class ITriplet
{
public:



// (Le code de Fabien ne compile pas : NB_VALEURS n'est
// encore défini)
// ...

char const * GetName() const {

return static_cast< T const & >(*this).name_;
}

// ...
};

class Essai: public ITriplet<Essai>
{



// ...
public:

static char name_[];
};



char Essai::name_[]= "Essai";

Manquerait plus qu'à comprendre comment je peux instancier un
template à partir d'un type qui n'est pas encore défini, afin
d'hériter de ce type instancié et donc finalement hériter de soit
même...
|-o
Comment ça marche ?


Ça marche pas.
--
Franck Branjonneau


Avatar
Fabien LE LEZ
On Mon, 12 Jul 2004 15:50:23 +0200, Franck Branjonneau
:

// (Le code de Fabien ne compile pas : NB_VALEURS n'est
// encore défini)


Le code suivant n'est pas correct ?

struct C
{
int f() { return valeur; }
enum { valeur= 42 };
};

Et quid de celui-là ?

struct C
{
int f() { return valeur; }
static int valeur;
};

int C::valeur= 42;

Avatar
Franck Branjonneau
Fabien LE LEZ écrivait:

On Mon, 12 Jul 2004 15:50:23 +0200, Franck Branjonneau
:

// (Le code de Fabien ne compile pas : NB_VALEURS n'est
// encore défini)


Le code suivant n'est pas correct ?

struct C
{
int f() { return valeur; }
enum { valeur= 42 };
};


Correct.

Et quid de celui-là ?

struct C
{
int f() { return valeur; }
static int valeur;
};

int C::valeur= 42;


Correct.

template< typename >
struct C
{
int f() { return valeur; }
enum { valeur= 42 };
};

Incorrect, même si il compile.
--
Franck Branjonneau


Avatar
Fabien LE LEZ
On Mon, 12 Jul 2004 17:04:11 +0200, Franck Branjonneau
:

template< typename >
struct C
{
int f() { return valeur; }
enum { valeur= 42 };
};

Incorrect


Grrr...
Y a-t-il moyen de s'en sortir quand on veut écrire :

template< typename >
struct C
{
int f() { return taille; }
double table [taille];
};

?

Avatar
drkm
"Aurélien REGAT-BARREL" writes:

Bonjour à tous,


[...]

Je serais plutôt d'accord avec Fabien pour dire qu'il s'agit surtout
d'une question de documentation. Mais je n'ai pas beaucoup de données
sur l'utilisation que tu compte faire de ces classes.

Si tu veux avoir une classe définissant les comportement que doit
fournir un triplet (le ctor, getValue() et getName()), avec certaines
actions communes entre tous les types de triplets (comme la
varification de l'indice dans getValue()), sans spécifier de structure
physique ou de relation d'héritage, l'utilisation de Policy Classes
peut être intéressant.

Encore une fois, je ne sais pas l'utilisation que tu veux faire de
ces classes, ni si elles se limitent effectivement à l'interface que
tu as donné. Je ne sais donc pas s'il s'agit d'une bonne idée pour
toi. Mais cela peut être intéressant de considérer les classes
suivantes.

TripletHost<> factorise les traitements communs à tous les triplets.
Il s'appuie de plus sur trois politiques, celle spécifiant le stockage
effectif des données, celle spécifiant les vérifications des indices
passés à getValue(), et celle spécifiant le nom.

Ces trois politiques ont une implémentation par défaut. Je montre
également la facilité de définition de politiques différentes.

~> cat fclcxx.cc
#include <iostream>
#include <ostream>
#include <stdexcept>


template < typename T >
class TripletDefaultStorage
{
protected:
TripletDefaultStorage(
T first , T second , T third
)
{
myDatas[ 0 ] = first ;
myDatas[ 1 ] = second ;
myDatas[ 2 ] = third ;
}
T getValue( int index ) const {
return myDatas[ index ] ;
}
private:
T myDatas[ 3 ] ;
} ;

template < typename T >
class TripletDefaultName
{
protected:
static std::string getName() {
return "My default name" ;
}
} ;

class TripletDoCheck
{
protected:
static void check( int index ) {
if ( index < 0 || index > 2 ) {
throw std::out_of_range( "TripletHost<>: range error !" ) ;
}
}
} ;

class TripletNoCheck
{
protected:
static void check( int index ) {
}
} ;

template <
typename Type ,
template< typename T > class StoragePolicy = TripletDefaultStorage ,
template< typename T > class NamePolicy = TripletDefaultName ,
class CheckPolicy = TripletDoCheck

class TripletHost

: public StoragePolicy< Type >
, public NamePolicy< Type >
, public CheckPolicy
{
public:
typedef Type element_type ;
typedef StoragePolicy< Type > storage_type ;
typedef NamePolicy< Type > name_type ;
typedef CheckPolicy check_type ;

TripletHost(
element_type first ,
element_type second ,
element_type third
)
: storage_type( first , second , third )
{
}

element_type getValue( int index ) const {
check_type::check( index ) ;
return storage_type::getValue( index ) ;
}

static std::string getName() {
return name_type::getName() ;
}
} ;


template < typename T >
class MyStorage
{
protected:
MyStorage(
T first , T second , T third
)
: myFirst( first )
, mySecond( second )
, myThird( third )
{
}
T getValue( int index ) const {
return 0 == index
? myFirst
: 1 == index
? mySecond
: myThird ;
}
private:
T myFirst ;
T mySecond ;
T myThird ;
} ;

template < typename T >
class MyName
{
protected:
static std::string getName() {
return "Mon nom à moi" ;
}
} ;


template <
typename T ,
template< typename T > class S ,
template< typename T > class N ,
class C

void

showTriplet(
TripletHost<T,S,N,C> const & t ,
std::string const & var_name
)
{
std::cout
<< var_name
<< " name : "
<< t.getName()
<< ", first : "
<< t.getValue( 0 )
<< ", second : "
<< t.getValue( 1 )
<< ", third : "
<< t.getValue( 2 )
<< std::endl ;
try {
double fourth( t.getValue( 3 ) ) ;
std::cout << " fourth : " << fourth << std::endl ;
}
catch ( std::exception & e ) {
std::cerr << " exception : " << e.what() << std::endl ;
}
}


int main()
{
typedef TripletHost< double > DoubleTriplet ;
typedef TripletHost< double , MyStorage , MyName , TripletNoCheck >
MyTriplet ;
typedef TripletHost<
double ,
TripletDefaultStorage ,
MyName ,
TripletNoCheck
DangerousTriplet ;


DoubleTriplet t1( 0.0 , 1.0 , 2.0 ) ;
MyTriplet t2( 0.5 , 1.5 , 2.5 ) ;
DangerousTriplet t3( 3 , 4 , 5 ) ;

showTriplet( t1 , "t1" ) ;
showTriplet( t2 , "t2" ) ;
showTriplet( t3 , "t3" ) ;
}
~> g++ -o fclcxx fclcxx.cc -Wall -ansi -pedantic
~> ./fclcxx
t1 name : My default name, first : 0, second : 1, third 2
exception : TripletHost<>: range error !
t2 name : Mon nom à moi, first : 0.5, second : 1.5, third 2.5
fourth : 2.5
t3 name : Mon nom à moi, first : 3, second : 4, third 5
fourth : -1.78627

À noter : les deux affichages de « fourth ».

--drkm

Avatar
Aurélien REGAT-BARREL
Et pire encore :

template <class T> class ITriplet
{
public:
char const * GetName() const {



return static_cast< T const & >(*this).name_;
}



Et à ça a une utilité par rapport à return T::name_ ? (mis à part
l'obfuscation de code biensûr :-)

Manquerait plus qu'à comprendre comment je peux instancier un
template à partir d'un type qui n'est pas encore défini, afin
d'hériter de ce type instancié et donc finalement hériter de soit
même...
|-o
Comment ça marche ?


Ça marche pas.


Ben pour moi c'est ce que tu viens d'écrire :

class Essai: public ITriplet<Essai>

Je pige pas ce qui se passe.

--
Aurélien REGAT-BARREL



Avatar
Franck Branjonneau
Fabien LE LEZ écrivait:

On Mon, 12 Jul 2004 17:04:11 +0200, Franck Branjonneau
:

[ Des conneries.]

Y a-t-il moyen de s'en sortir


Tu fais comme dans ton code initial -- je deviens parano à surveiller
le code template.
--
Franck Branjonneau

Avatar
Aurélien REGAT-BARREL
Je serais plutôt d'accord avec Fabien pour dire qu'il s'agit surtout
d'une question de documentation. Mais je n'ai pas beaucoup de données
sur l'utilisation que tu compte faire de ces classes.


Oui, c'est ce que je comptais faire jusqu'au moment de coder. Une chose me
gêne : 50% du code des tous les triplets est toujours le même (stockage des
3 valeurs, accès aux 3 valeurs, ...), je cherche à factoriser ces 50%...

Si tu veux avoir une classe définissant les comportement que doit
fournir un triplet (le ctor, getValue() et getName()), avec certaines
actions communes entre tous les types de triplets (comme la
varification de l'indice dans getValue()), sans spécifier de structure
physique ou de relation d'héritage, l'utilisation de Policy Classes
peut être intéressant.

Encore une fois, je ne sais pas l'utilisation que tu veux faire de
ces classes, ni si elles se limitent effectivement à l'interface que
tu as donné. Je ne sais donc pas s'il s'agit d'une bonne idée pour
toi. Mais cela peut être intéressant de considérer les classes
suivantes.


C'est la suite de mon post sur la définition d'une interface de manipulation
des données auquel tu as participé d'ailleurs. J'étais parti sur une classe
de base Triplet dérivée autant de fois que nécessaire, mais je me suis
apperçu que ça n'allait pas.
- En théorie, j'ai autant de classes que de types de triplets, chacune n'a
rien en commun avec les autres. Le seul point commun est le nom des méthodes
afin d'avoir des fonctions génériques pour l'affichage, etc...
- En pratique, plus de 50% du code est identique. Je veux juste trouver une
manière d'isoler le code commun sans toucher au fait que chaque triplet est
indépendant des autres (pas de parent commun notamment).

TripletHost<> factorise les traitements communs à tous les triplets.
Il s'appuie de plus sur trois politiques, celle spécifiant le stockage
effectif des données, celle spécifiant les vérifications des indices
passés à getValue(), et celle spécifiant le nom.


L'héritage multiple j'y avais pas pensé. Mais je saisi pas bien l'avantage
par rapport à mon exemple n°2 où ce n'est pas de l'héritage multiple de
classes mais un template acceptant plusieurs functors. Le principe du
wrapper me parraît commun aux 2 approches, sauf que dans un cas j'ai 3
fonctions à écrire, et dans le tiens 3 classes, + une syntaxe de templates
assez lourde. Déjà que la liste de functors je trouvais ça moyen, là ça me
parrait un peu trop chargé.

En fait, tu as créé un triplet hyper générique pour un type T. Moi j'ai
toujours un triplet de double, et je veux pas cistomiser l'accès et le
stockage, au contraire, je veux les centraliser. La seule chose à
customiser, c'est le calcul de leur valeur, et leur nom (individuel et
collectif). Il n'y a rien d'autre.
Le but n'est pas de créer quelque chose d'hyper générique qui va être à la
base de tout un tas de classes, mais plutôt de mettre en commun le code de 5
ou 6 classes, et de faciliter l'ajout d'une nouvelle.
Je pourrais parfaitement copier-coller le code a chaque fois, je cherche
juste quelque chose de plus élégant.

*********
En fait, la condition est :
- il est possible de faire de l'héritage à condition qu'aucun triplet
n'ai de parent en commun (héritage sans polymorphisme)
*********

C'est pour cela que le coup du
class ABC : public ITriplet<ABC>
me plaisais bien, car y'a aucun lien de parenté entre ABC et DEF, et
pourtant tous les triplets partagent le code de ITriplet.

Le problème est donc : partager du code + des fonctions membres entre
plusieurs classes sans que celles-ci n'aient de parent commun.

--
Aurélien REGAT-BARREL

Avatar
Aurélien REGAT-BARREL
Si je récapitule, le problème est :
partager une structure (stockage des triplets) + du code (fonctions
membres d'accès aux valeurs) + une interface (noms de fonctions) entre
plusieurs classes sans que celles-ci n'aient de parent commun.
Il est possible de faire de l'héritage à condition qu'aucun triplet n'ai de
parent en commun.

Actuellement, je penche pour
class ABC : public ITriplet<ABC>

avec ABC contenant les std::string + la méthode de calcul propres à ce
triplet.

--
Aurélien REGAT-BARREL
Avatar
Fabien LE LEZ
On Mon, 12 Jul 2004 15:44:36 +0200, "Aurélien REGAT-BARREL"
:

Oui, mais quand tu écris ITriplet<ABC>, le template ITriplet va être
instancié avec ABC, or ABC n'est pas encore implémenté...


En fait, je ne sais pas du tout si c'est légal, mais ça a l'air de
marcher. Y a-t-il quelqu'un dans la salle qui sache comment
fonctionnent les templates ?

Il y a une astuce là-dedans :

template <class T> class ITriplet
{ ... };

Le "T" n'est jamais utilisé. Du coup, ITriplet<ABC> peut (en théorie)
être instancié sans rien connaître de ABC.


struct A;
struct B
{
void f (A const&);
};

Maintenant, comme je l'ai dit, je ne sais pas trop si ça fonctionne
réellement.
On peut le remplacer par :

template <int anti_doublons> class ITriplet...

avec le paramètre de template différent à chaque instanciation.

Le paramètre de template n'est pas vraiment un paramètre, il s'agit
juste de s'assurer que les différentes classes "triplets" ne dérivent
pas d'une base commune.

Finalement, il a un peu le même rôle que le paramètre entier de

C& operator ++ (int) // c++

i.e. il évite au compilo de confondre avec

C& operator ++() // ++c

1 2 3 4 5