Definition d'une interface (a la Java) a l'aide de templates
62 réponses
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)
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.
class ABC : public ITriplet<ABC> { private: friend ITriplet<ABC>; static const char TripletName[]; };
Mais c'est moyen.
Tu parles du « friend » ? Il me semble que c'est pourtant une technique répandue.
--drkm
Aurélien REGAT-BARREL
Le seul problème ici, c'est que tu écris tes variables de la même façon que tes fonctions.
Par exemple, le code suivant ne me choque pas :
class C { public: std::string GetTripletName() const { return triplet_name; } private: static char const* triplet_name; };
Moi non plus ça ne me choque pas, sauf qu'ici on a :
class C { public: std::string GetTripletName() const { return triplet_name; } // hérité static char const* triplet_name; };
Tout est public. triplet_name de devrait pas être utilisé et donc utilisable, vu que l'interface définit GetTripletName. Alors on peut s'en sortir avec friend :
class ABC : public ITriplet<ABC> { private: friend ITriplet<ABC>; static const char TripletName[]; };
Mais c'est moyen.
-- Aurélien REGAT-BARREL
Le seul problème ici, c'est que tu écris tes variables de la même
façon que tes fonctions.
Par exemple, le code suivant ne me choque pas :
class C
{
public:
std::string GetTripletName() const { return triplet_name; }
private:
static char const* triplet_name;
};
Moi non plus ça ne me choque pas, sauf qu'ici on a :
class C
{
public:
std::string GetTripletName() const { return triplet_name; } // hérité
static char const* triplet_name;
};
Tout est public.
triplet_name de devrait pas être utilisé et donc utilisable, vu que
l'interface définit GetTripletName.
Alors on peut s'en sortir avec friend :
Le seul problème ici, c'est que tu écris tes variables de la même façon que tes fonctions.
Par exemple, le code suivant ne me choque pas :
class C { public: std::string GetTripletName() const { return triplet_name; } private: static char const* triplet_name; };
Moi non plus ça ne me choque pas, sauf qu'ici on a :
class C { public: std::string GetTripletName() const { return triplet_name; } // hérité static char const* triplet_name; };
Tout est public. triplet_name de devrait pas être utilisé et donc utilisable, vu que l'interface définit GetTripletName. Alors on peut s'en sortir avec friend :
On Tue, 13 Jul 2004 18:13:20 +0200, "Aurélien REGAT-BARREL" :
Tout est public.
Arf...
En passant, ne perds pas de vue <news:...
Aurélien REGAT-BARREL
Mais c'est moyen.
Tu parles du « friend » ? Il me semble que c'est pourtant une technique répandue.
Ah bon. A vu de nez ça me parrait pas terrible, et à l'école on m'a fortement déconseillé l'usage de friend. Je me demande si l'héritage privé ne convient pas mieux ici, pour marquer le fait que ABC n'est pas un Triplet (kind of) mais implémente une pseudo interface (souligné par l'utilisation de using). Exemple :
template<typename T> class ITriplet { public: inline double GetValue( int Index ) const { return this->values.at( Index ); }
std::string GetCoordName( int Index ) { if ( Index >= 0 && Index < 3 ) { return T::CoordNames[ Index ]; } throw std::out_of_range( "ITriplet<>::GetCoordName : out of range!" ); }
int main() { ABC abc( 0, 1, 2 ); std::cout << abc.GetTripletName() << 'n'; for ( int i = 0; i < 3; ++i ) { std::cout << 't' << abc.GetCoordName( i ) << " : " << abc.GetValue( i ) << 'n'; } }
-- Aurélien REGAT-BARREL
Mais c'est moyen.
Tu parles du « friend » ? Il me semble que c'est pourtant une
technique répandue.
Ah bon. A vu de nez ça me parrait pas terrible, et à l'école on m'a
fortement déconseillé l'usage de friend.
Je me demande si l'héritage privé ne convient pas mieux ici, pour marquer le
fait que ABC n'est pas un Triplet (kind of) mais implémente une pseudo
interface (souligné par l'utilisation de using). Exemple :
template<typename T>
class ITriplet
{
public:
inline double GetValue( int Index ) const
{
return this->values.at( Index );
}
std::string GetCoordName( int Index )
{
if ( Index >= 0 && Index < 3 )
{
return T::CoordNames[ Index ];
}
throw std::out_of_range( "ITriplet<>::GetCoordName : out of
range!" );
}
Tu parles du « friend » ? Il me semble que c'est pourtant une technique répandue.
Ah bon. A vu de nez ça me parrait pas terrible, et à l'école on m'a fortement déconseillé l'usage de friend. Je me demande si l'héritage privé ne convient pas mieux ici, pour marquer le fait que ABC n'est pas un Triplet (kind of) mais implémente une pseudo interface (souligné par l'utilisation de using). Exemple :
template<typename T> class ITriplet { public: inline double GetValue( int Index ) const { return this->values.at( Index ); }
std::string GetCoordName( int Index ) { if ( Index >= 0 && Index < 3 ) { return T::CoordNames[ Index ]; } throw std::out_of_range( "ITriplet<>::GetCoordName : out of range!" ); }
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 >;
}
namespace B {
struct V: U< V > {
};
}
Exact ? -- Franck Branjonneau
Fabien LE LEZ
On Tue, 13 Jul 2004 18:36:46 +0200, "Aurélien REGAT-BARREL" :
Ah bon. A vu de nez ça me parrait pas terrible, et à l'école on m'a fortement déconseillé l'usage de friend.
Disons que friend est d'usage assez peu courant, parce que généralement il y a une meilleure solution. Sauf que le cas que tu nous expose est un peu tordu (i.e. interface sans polymorphisme), donc en l'absence d'autre solution, "friend" est tout à fait acceptable.
Il est vrai que "friend" a tendance à briser l'encapsulation si on l'utilise entre deux classes n'ayant rien à voir, mais ce n'est pas le cas ici, puisque c'est la classe de base qui est déclarée "friend".
On Tue, 13 Jul 2004 18:36:46 +0200, "Aurélien REGAT-BARREL"
<nospam-aregatba@yahoo.fr.invalid>:
Ah bon. A vu de nez ça me parrait pas terrible, et à l'école on m'a
fortement déconseillé l'usage de friend.
Disons que friend est d'usage assez peu courant, parce que
généralement il y a une meilleure solution. Sauf que le cas que tu
nous expose est un peu tordu (i.e. interface sans polymorphisme), donc
en l'absence d'autre solution, "friend" est tout à fait acceptable.
Il est vrai que "friend" a tendance à briser l'encapsulation si on
l'utilise entre deux classes n'ayant rien à voir, mais ce n'est pas le
cas ici, puisque c'est la classe de base qui est déclarée "friend".
On Tue, 13 Jul 2004 18:36:46 +0200, "Aurélien REGAT-BARREL" :
Ah bon. A vu de nez ça me parrait pas terrible, et à l'école on m'a fortement déconseillé l'usage de friend.
Disons que friend est d'usage assez peu courant, parce que généralement il y a une meilleure solution. Sauf que le cas que tu nous expose est un peu tordu (i.e. interface sans polymorphisme), donc en l'absence d'autre solution, "friend" est tout à fait acceptable.
Il est vrai que "friend" a tendance à briser l'encapsulation si on l'utilise entre deux classes n'ayant rien à voir, mais ce n'est pas le cas ici, puisque c'est la classe de base qui est déclarée "friend".
Aurélien REGAT-BARREL
Disons que friend est d'usage assez peu courant, parce que généralement il y a une meilleure solution. Sauf que le cas que tu nous expose est un peu tordu (i.e. interface sans polymorphisme), donc en l'absence d'autre solution, "friend" est tout à fait acceptable.
Il est vrai que "friend" a tendance à briser l'encapsulation si on l'utilise entre deux classes n'ayant rien à voir, mais ce n'est pas le cas ici, puisque c'est la classe de base qui est déclarée "friend".
Ok merci. Et que penses-tu de l'héritage privé ?
-- Aurélien REGAT-BARREL
Disons que friend est d'usage assez peu courant, parce que
généralement il y a une meilleure solution. Sauf que le cas que tu
nous expose est un peu tordu (i.e. interface sans polymorphisme), donc
en l'absence d'autre solution, "friend" est tout à fait acceptable.
Il est vrai que "friend" a tendance à briser l'encapsulation si on
l'utilise entre deux classes n'ayant rien à voir, mais ce n'est pas le
cas ici, puisque c'est la classe de base qui est déclarée "friend".
Disons que friend est d'usage assez peu courant, parce que généralement il y a une meilleure solution. Sauf que le cas que tu nous expose est un peu tordu (i.e. interface sans polymorphisme), donc en l'absence d'autre solution, "friend" est tout à fait acceptable.
Il est vrai que "friend" a tendance à briser l'encapsulation si on l'utilise entre deux classes n'ayant rien à voir, mais ce n'est pas le cas ici, puisque c'est la classe de base qui est déclarée "friend".
Ok merci. Et que penses-tu de l'héritage privé ?
-- Aurélien REGAT-BARREL
Patrick \Zener\ Brunet
Bonjour.
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 ? Sinon :
Avant que ne se généralisent les ATL, l'implémentation de COM (de Microsoft, mais on se restreindra à la technique C++) était faite à partir de classes imbriquées qui étaient des "implémenteurs" pour des interfaces définies par des struct IMachin contenant des virtuelles pures.
Chaque implémenteur pour passer à la compile devait donc fournir la totalité des fonctions de l'interface, sous forme idéalement de mapping sur les fonctions privées de la classe englobante.
L'inconvénient de cette technique était de devoir remonter depuis l'implémenteur au this de la classe englobante au moyen d'une macro basée sur offsetof().
L'avantage était donc de forcer une implémentation complète de l'interface sans préjuger du comment et surtout sans aucun risque de télescopage des méthodes d'interfaces diverses (contrairement à la technique de l'héritage multiple et direct par la classe devant implémenter l'interface).
En lisant le bouquin de Dave Chappell (Au coeur d'ActiveX et OLE), j'avais trouvé ça à la fois plutôt esthétique et pratique. Bien sûr les wizards pour programmer à votre place en prennent un coup ;-)
Hope It Helps.
Cordialement, PZB
"Aurélien REGAT-BARREL" a écrit dans le message de news:40f2592d$0$25747$
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)
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
Bonjour.
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 ?
Sinon :
Avant que ne se généralisent les ATL, l'implémentation de COM (de Microsoft,
mais on se restreindra à la technique C++) était faite à partir de classes
imbriquées qui étaient des "implémenteurs" pour des interfaces définies par
des struct IMachin contenant des virtuelles pures.
Chaque implémenteur pour passer à la compile devait donc fournir la totalité
des fonctions de l'interface, sous forme idéalement de mapping sur les
fonctions privées de la classe englobante.
L'inconvénient de cette technique était de devoir remonter depuis
l'implémenteur au this de la classe englobante au moyen d'une macro basée
sur offsetof().
L'avantage était donc de forcer une implémentation complète de l'interface
sans préjuger du comment et surtout sans aucun risque de télescopage des
méthodes d'interfaces diverses (contrairement à la technique de l'héritage
multiple et direct par la classe devant implémenter l'interface).
En lisant le bouquin de Dave Chappell (Au coeur d'ActiveX et OLE), j'avais
trouvé ça à la fois plutôt esthétique et pratique. Bien sûr les wizards pour
programmer à votre place en prennent un coup ;-)
Hope It Helps.
Cordialement,
PZB
"Aurélien REGAT-BARREL" <nospam-aregatba@yahoo.fr.invalid> a écrit dans le
message de news:40f2592d$0$25747$626a14ce@news.free.fr...
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)
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.
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 ? Sinon :
Avant que ne se généralisent les ATL, l'implémentation de COM (de Microsoft, mais on se restreindra à la technique C++) était faite à partir de classes imbriquées qui étaient des "implémenteurs" pour des interfaces définies par des struct IMachin contenant des virtuelles pures.
Chaque implémenteur pour passer à la compile devait donc fournir la totalité des fonctions de l'interface, sous forme idéalement de mapping sur les fonctions privées de la classe englobante.
L'inconvénient de cette technique était de devoir remonter depuis l'implémenteur au this de la classe englobante au moyen d'une macro basée sur offsetof().
L'avantage était donc de forcer une implémentation complète de l'interface sans préjuger du comment et surtout sans aucun risque de télescopage des méthodes d'interfaces diverses (contrairement à la technique de l'héritage multiple et direct par la classe devant implémenter l'interface).
En lisant le bouquin de Dave Chappell (Au coeur d'ActiveX et OLE), j'avais trouvé ça à la fois plutôt esthétique et pratique. Bien sûr les wizards pour programmer à votre place en prennent un coup ;-)
Hope It Helps.
Cordialement, PZB
"Aurélien REGAT-BARREL" a écrit dans le message de news:40f2592d$0$25747$
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)
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.