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
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é...


Puisque

class ABC;
class Machin
{
ABC* ptr;
};

est valide, il me semble que ceci doit être valide aussi :

template <class T> class Machin
{
T* ptr;
};

class ABC: public Machin <ABC>
{
...

Avatar
drkm
Franck Branjonneau writes:

Fabien LE LEZ écrivait:

Le code suivant n'est pas correct ?

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


Correct.

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

Incorrect, même si il compile.


À cause du modèle ? Je ne comprend pas. Pourrais-tu préciser un
peu, stp ?

--drkm


Avatar
Aurélien REGAT-BARREL
Puisque

class ABC;
class Machin
{
ABC* ptr;
};

est valide, il me semble que ceci doit être valide aussi :

template <class T> class Machin
{
T* ptr;
};

class ABC: public Machin <ABC>
{
...



Ah oui, vu comme ça alors ça marche. Ce n'est pas tout à fait ce que j'ai,
mais comme j'utilise des fonctions membres statiques, ça marche aussi. C'est
assez magique quand même.

--
Aurélien REGAT-BARREL

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

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.


Je n'ai pas vraiment le temps de relire les articles en rapport avec
tes triplets. Mais si je comprend bien, tu voudrais simplement faire
des types différents, au point même de ne pas pouvoir les utiliser via
un type de base, alors que les traitements et la structure sont
identiques (mais pas la sémantique, donc).

Que penses-tu de quelque chose comme ceci ?

namespace MyLib
{
template < typename Tag >
class TypeFactorisé
{
// ...
} ;

struct TypeSpécifique1Tag {} ;
struct TypeSpécifique2Tag {} ;
// ...

typedef TypeFactorisé< TypeSpécifique1Tag > TypeSpécifique1 ;
typedef TypeFactorisé< TypeSpécifique2Tag > TypeSpécifique2 ;
// ...
}

Si tu veux définir des algorithmes ne traitant que des
instantiations de TypeFactorisé, tu peux utiliser

namespace MyLib
{
template < typename Tag >
ReturnType
monAlgo( TypeFactorisé< Tag > const & param ) {
// ...
}
}

Tu peux toujours, le cas échéant, traiter les quelques exceptions
par des spécialisations sur les différents tags.

--drkm

Avatar
Loïc Joly
Fabien LE LEZ wrote:

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 ?


Je sais pas trop comment, mais en tout cas, je sais que ça marche. Ca
porte même un nom : CRTP ( Curiously Recurring Template Pattern).
Regarde par exemple :
http://www.informit.com/articles/article.asp?p1473&seqNum=3

(c'est extrait du livre de Jossutis et Vandevoorde sur les templates)

Remarque : Ca ne me choque pas que ça marche, quand on lit :
9 Classes
[...]
2 A classname is inserted into the scope in which it is declared
immediately after the classname is seen. The classname is also inserted
into the scope of the class itself.

struct A
{
A(int i) : myI(i);
A f() {return A(i+42);}
int myI;
};

On utilise bien A alors qu'elle pourrait sembler ne pas encore être définie.

--
Loïc


Avatar
drkm
Fabien LE LEZ writes:

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.


Je dirais, mais sans vérifier, qu'il faut que ABC soit déclarée. On
peut également utiliser un pointeur sur ABC avant sa définition, mais
pas un objet. Et ceci à l'endroit d'instanciation du template.

--drkm


Avatar
Fabien LE LEZ
On Mon, 12 Jul 2004 19:38:06 +0200, "Aurélien REGAT-BARREL"
:

C'est assez magique quand même.


Toute technologie suffisamment avancée ressemble à de la magie ;-)

Avatar
Fabien LE LEZ
On Mon, 12 Jul 2004 19:36:36 +0200, drkm :

Pourrais-tu préciser un
peu, stp ?


Je crois bien qu'il a dit s'être trompé.

Avatar
Franck Branjonneau
drkm écrivait:

Franck Branjonneau writes:

[ Des conneries. ]


Comme je l'ai dit un peu plus loin.
--
Franck Branjonneau

Avatar
Franck Branjonneau
"Aurélien REGAT-BARREL" écrivait:

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 :-)


La généricité ?

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.


Tou ce passe comme si tu avais :

template< typename > ITriplet {};

class Essai;

// Intanciation explicite
template class ITriplet< Essai >;

class Esssai: ITriplet< Essai > {};
--
Franck Branjonneau




1 2 3 4 5