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

3 4 5 6 7
Avatar
drkm
"Aurélien REGAT-BARREL" writes:

template<typename T>
class ITriplet
{
public:
static std::string GetTripletName()
{
return T::TripletName;
}
};

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

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

template<typename T>
class ITriplet
{
public:
static std::string GetTripletName()
{
return T::TripletName;
}
};

class ABC : public ITriplet<ABC>
{
private:
friend ITriplet<ABC>;
static const char TripletName[];
};

Mais c'est moyen.

--
Aurélien REGAT-BARREL

Avatar
Fabien LE LEZ
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:...

Avatar
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!" );
}

std::string GetTripletName()
{
return T::TripletName;
}

protected:
void Initialize( double A, double B, double C )
{
values.resize( 3 );
values[ 0 ] = A;
values[ 1 ] = B;
values[ 2 ] = C;
}

private:
std::vector<double> values;
};


class ABC : private ITriplet<ABC>
{
public:
ABC( double A, double B, double C )
{
ITriplet<ABC>::Initialize( A, B, C );
}

using ITriplet<ABC>::GetTripletName;
using ITriplet<ABC>::GetCoordName;
using ITriplet<ABC>::GetValue;

// implémentation de l'interface ITriplet
private:
friend ITriplet<ABC>;
static const char TripletName[];
static const char CoordNames[][ 3 ];
};

const char ABC::TripletName[] = "A.B.C";
const char ABC::CoordNames[][ 3 ] = { "A", "B", "C" };


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


Avatar
Aurélien REGAT-BARREL
Je pense qu'ainsi l'aspect "classe utilitaire" de ITriplet est plus mis en
avant.

--
Aurélien REGAT-BARREL
Avatar
google
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 ...

Dans ton exemple, ce point est entre les deux definitions.

David

Avatar
Franck Branjonneau
(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 >;

}

namespace B {

struct V: U< V > {

};

}

Exact ?
--
Franck Branjonneau


Avatar
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".

Avatar
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

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

#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




3 4 5 6 7