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

2 réponses

3 4 5 6 7
Avatar
Aurélien REGAT-BARREL
Mis à part le surcroît de taille et éventuellement la non relogeabilité,
avez-vous une raison impérieuse de ne pas vouloir de fonction virtuelles ?



Ce n'est pas les fonctions virtuelles qui me gênent, mais le fait qu'il y
ait une classe de base commune : je ne veux pas de polymorphisme.
Le fait que ABC et DEF sont des triplets est une caractéristique, et pas une
consistance.
Je veux dire par là que c'est presque un hasard. Ces classes ont 3 valeurs,
mais qui n'ont rien à voir.
A aucun endroit dans le code (client) je veux voir la classe ITriplet.
Manipuler des ITriplet n'a pas de sens.
Il faut voir Triplet comme une classe "helper", sur le diagramme des classes
général (analyse) elle ne figure d'ailleurs pas. C'est un détail
d'implémentation. On n'est même pas obligé de l'utiliser. Elle sert à
réduire le code à écrire et à assurer d'avoir les bonnes fonctions
(interface) qui sont appelées ailleurs dans du code.

Sinon, qu'est-ce que la relogeabilité ? Ca a un rapport avec la relocation
de code (dll) sous Windows ou... ?

--
Aurélien REGAT-BARREL

Avatar
google
Franck Branjonneau wrote:
(Daveed Vandevoorde) écrivait:

Franck Branjonneau wrote:
[...]
Quand on écrit

template< typename >
struct U {

};

struct V: U< V > {

};

Il y a /instanciation implicite/ de U avec le paramètre V car hériter
d'une classe nécessite que cette classe soit définie.


Oui.

Le /point d'instanciation/ se situe avant "l'enclosing namespace"
du machin qui à déclencher l'instanciation.


Presque: Il se situe _dans_ "l'enclosing namespace" (juste) _avant_ le
machin ...


Presque: avant le machin, dans le namespace où il est défini.

C'est à dire que dans

namespace A {

template< typename >
struct U {

};

}

namespace B {

struct V: U< V > {

};

}

tout se passe comme si j'avais écrit

namespace A {

template< typename >
struct U {

};

}

namespace B {

struct V;

}

namespace A {

template<> struct U< V >;
(Remplacer V par B::V.)


}

namespace B {

struct V: U< V > {

};

}

Exact ?


Essentiellement, oui. (Une specialization explicite n'est jamais
exactement equivalente a une instantiation implicite mais on
fera avec pour la notation.)

David



3 4 5 6 7