J'aimerais savoir s'il est possible de créer une classe template qui oblige l'utilisateur à utiliser comme type générique une classe implémentant une certaine interface, et si oui comment... Je m'explique ! Je voudrais avoir une classe template A<T> avec T qui implémente obligatoirement une interface I ! J'espère que vous m'avez compris, n'hésitez pas à me demander de préciser !
Il est possible d'imposer qu'un paramètre template dérive d'une classe de base
class A{/* on s'en fout*/};
class B : public A {/* idem*/ };
class C{/* idem*/};
template<class X>
struct TX
{
static_assert(std::is_base_of<A,X>::value,"X must derive from A");
// ...
};
TX<B> txb; // ok
TX<C> txc; // erreur!
Merci beaucoup ! Si ça ne dérive pas du type de base, ça criera à la compilation ? À l'exécution ? Est-ce que dans ma classe template j'aurai le droit d'appeler une fonction qui n'existe que pour X ? Le compilateur ne criera pas ?
Non et c'est quelque part le but, si j'impose que le paramètre template dérive d'une classe, c'est forcément parce que mon template a besoin d'utiliser les fonctions de cette classe mère (sinon pourquoi imposer cette restriction?).
C'est peut être un peu naïf, mais si tu as impérativement besoin de dériver d'une classe mère, pour utiliser l'interface commune de cette classe, est ce qu'on ne rentre pas dans le cas des fonctions virtuelles?
Peut-être que je me suis trompé au niveau de la conception...
J'ai une classe totalement abstraite, une interface donc, qui possède un champ info, ensuite j'ai une autre interface qui possède une méthode qui doit récupérer le champ info de la première interface. De plus, j'ai une interface qui représente une info. Ces trois interfaces sont donc destinées à être implémentées par des classes concrètes. J'ai donc décidé de faire la première interface une classe template, avec un type générique dérivant obligatoirement de Info. Donc lorsque je spécialiserai les interfaces en les implémentant, je spécialiserai aussi le type générique.. J'espère que j'ai été clair! :s
C'est peut être un peu naïf, mais si tu as impérativement besoin de dériver d'une classe mère, pour utiliser l'interface commune de cette classe, est ce qu'on ne rentre pas dans le cas des fonctions virtuelles?
Pourquoi les templates ici?
C'est assez juste ce que tu dis là, j'ai réduit un bout de code sans penser à ce détail. Le code dont j'ai tiré mon exemple utilise 3 paramètres template (le 3ème est un variadic), l'idée de base est de pouvoir écrire quelque chose comme ça
std::unique_ptr<B> ptr = make_unique_base<B,D>(arguments pour un constructeur de D);
Cette construction n'a un sens que si D dérive de B.
@int21h: Dans ton premier message, tu montres un exemple d'utilisation avec une structure, mais avec une classe, on met bien l'assertion dans le constructeur ?
Dernière question, on dit souvent que l'on doit absolument déclarer les fonction template de classes template dans l'en-tête, mais il me semble qu'il y a des solutions pour les déclarer dans des fichiers sources, lesquelles ?
Le compilateur doit pouvoir lire le code du template pour pouvoir en créer une instance, il faut donc que ce code soit accessible partout où tu l'utilises, c'est la raison pour laquelle il est dans le header.
La seule astuce, que tu as est de séparer déclaration et implémentation dans de fichiers : le .hpp qui contient toutes les déclarations et le .tpp (ou .-ce-que-tu-veux-qui-n'est-pas-compilé) qui contient l'implémentation. Ton .tpp est inclu a la fin du .hpp, ce qui revient au final à la même chose que si tout était dans le .hpp
//banane.tpp
template<typename T void peler(T &t){
//implementation
t.pele_moi();
}
Pour ton design
Pas sur d'avoir tout compris J'ai une classe totalement abstraite, une interface donc, qui possède un champ info (I1), ensuite j'ai une autre interface(I2) qui possède une méthode(useInfo) qui doit récupérer le champ info de la première interface.
En quoi I2 est une interface? Pourquoi I1 qui est censée être une interface se trimbale des données membres (c'est un cas possible, mais faut une bonne raison de le faire)? Tu dis plus tard que Info est un type template... très bien mais pourquoi?
De plus, j'ai une interface qui représente une info.
Là encore pourquoi pas, mais euh... en vrai tu veux faire quoi?
Je ne suis pas sur que tu aie été clair, ni que tu utilise le mot interface pour la bonne raison. Est ce que tu veux bien en dire un peu plus sur ton vrai problème concret : qu'est ce qu'une Info en vrai, ce que tes interfaces sont censées faire, à quoi servent leurs méthodes?
- Edité par ledemonboiteux 4 novembre 2014 à 9:51:53
Peut-être que je me suis mal exprimé, alors je récapitule.
Je mets en place un dp Observer. Dans ma version, je crée une classe que l'on ne doit pas initialiser, Observable<Info>, Observeur<Info>, ainsi qu'une classe vide Info. Info représente le type d'informations que manipule l'Observable, et donc l'Observeur. Je ne permets pas à un Observeur d'observer plusieurs Observables. À l'utilisation, on crée son propre observable, héritant de Observable<le type d'informations que l'on veut>, ainsi qu'une classe observeur héritant de Observeur<le même type d'informations>, et on crée également une classe héritant d'Info pour les informations.
Ce n'est sûrement pas la meilleure manière de faire, ce n'est pas mon but, mais au moins pour le moment ça marche et c'est tout ce qui compte!
Non il n'y en a pas. pour une fois les on dit ont raison, les template c'est forcément header only.
A vrai dire, non, pas forcément... La seule obligation à laquelle tu sois soumis, c'est de veiller à ce que le compilateur dispose de l'implémentation des fonctions template lorsqu'il arrive à déterminer le type du paramètre template.
A partir de là, il y a un tas de trucs "sympa" qui deviennent possibles, sans vouloir être exhaustif :
La séparation des implémentations dans un fichier séparé (*.tpp ? , *.impl ?) qui ne sera inclus que dans les fichiers d'implémentation (*.cpp) qui en ont besoin
L'instantiation explicite de tes classes template avec des paramètres clairement définis, afin de provoquer la génération de ces "spécialisation" pour les utiliser dans une bibliothèque.
...
ledemonboiteux a écrit:
C'est peut être un peu naïf, mais si tu as impérativement besoin de dériver d'une classe mère, pour utiliser l'interface commune de cette classe, est ce qu'on ne rentre pas dans le cas des fonctions virtuelles?
Pourquoi les templates ici?
Pas forcément... Mais une explication s'impose!!! Le problème est que la notion d'interface telle que prônée par java et C# (entre autres) ne s'applique pas vraiment au C++.
En java (et en C#), une interface n'est qu'une classe "vidée de substance" qui ne peut disposer d'aucune donnée membre, ce qui fait que l'on est obligé de laisser l'implémentation des comportements qu'elle exporte aux classes qui implémenteront cette interface.
Cette différence entre une classe et une interface (particulièrement "artificielle" s'il en est) n'existe pas en C++, vu qu'il n'y a absolument aucun mot clé permettant de distinguer l'une de l'autre. Par contre, le but d'une interface est d'éviter d'obliger une hiérarchie de classes à se "coltiner" un ensemble de comportements qui ne seraient utiles qu'à une "minorité" de classes dérivées, entre autres, afin de respecter le LSP.
Ce qu'il y a de bien, en C++, c'est qu'on dispose du paradigme générique et que MaClasse<A> n'a absolument rien à voir (hormis bien sur les comportements qui sont exposés par MaClasse) avec MaClasse<B>. Et une classe template n'est qu'une classe "un peu particulière" dans le sens où, si l'on ne sait pas exactement quelles données seront manipulées (sera-ce un A, un B ou ... un XYZ ???) on sait par contre exactement comment ces données seront manipulées.
Ainsi, tu peux parfaitement envisager une interface (template) qui te permette de représenter le concept d'identifiable de "manière unique et non ambigüe" qui prendrait une forme proche de
template <typename T>
class Identifiable{
public:
/* les comportements qui nous intéressent */
std::string const & name() const{return name_;}
size_t id() const{return id_;}
/* constructeur et destructeurs protégés, pour obliger l'utilisation de l'interface comme
* classe de base (héritage multiple)
*/
protected:
/* C++11 inside */
Identifiable(std::string const & name):name_(name),
id_(std::hash<std::string>()(name)){}
~Identifiable(){}
private:
std::string name_;
size_t id_;
};
qui servirait d'interface à deux classes n'ayant aucun lien entre elles sous une forme proche de
class A : public Identifiable<A>{ //CRTP inside
/* ... */
};
/* ... */
class B : public Identifiable<B>{
/* ... */
};
Comme les comportements propres à l'interface Identifiables ne doivent pas être modifiés, il n'y a aucune raison de les déclarer comme étant des fonctions virtuelles
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
J'avais fait à peu près la même chose, mais j'ai utilisé un std::vector à la place de ton set, pourquoi est-ce que tu as choisi cela? Et le override c'est bien pour préciser que c'est une redéfinition de fonction, spécifique au C++11?
De plus j'ai un problème: si je crée un objet d'une classe héritant de AbstractSubject<le type d'info qui va bien>, ou de AbstractObserver<le même type d'info>, et que j'appelle à partir de lui une méthode de la classe abstraite, j'obtiens une erreur à la compilation qui me dit que ma classe fille ne possède pas cette méthode...
J'avais fait à peu près la même chose, mais j'ai utilisé un std::vector à la place de ton set, pourquoi est-ce que tu as choisi cela? Parce que je n'ai pas bien réfléchi a la question, je voulais être sur que les données dans data soient unique. Ca va marcher avec une vector, la seule différence est que tu peux enregistrer un même truc plusieurs fois.
Override veut dire que tu va redéfinir une fonction qui existe déjà. Ca évite de créer une surcharge parce que t'as oublié un const par exemple. Et oui, c'est du c++11, le code marche pareil si tu le mets pas.
Est ce que tu peux mettre ton code qui fait que ça plante s'il te plait, j'ai du mal a deviner ce que tu fais.
D'accord, mais les set sont plus lents en écriture ou non?
Alors voilà le code, pour l'instant je ne vais mettre que les headers, en enlevant les protection contre les inclusions multiples ainsi que les commentaires qui semblent inutiles pour le moment, histoire de ne pas plus encombrer le post qu'il ne l'est déjà...
class AbstractInfo {
protected:
AbstractInfo(void) {}
~AbstractInfo(void) {}
};
StringInfo.hxx
# include "AbstractInfo.hxx"
# include <string>
class StringInfo: public AbstractInfo {
public:
StringInfo(const std::string& str = "");
void setString(const std::string& str = "");
void clear(void);
std::string getString(void) const;
private:
std::string _str;
};
Si j'ai juste un main incluant les headers ci-dessus, on me dit:
In file included from src/StringObserver.cpp:1:0:
headers/StringObserver.hxx:7:30: error: invalid use of incomplete type ‘class AbstractSubject<StringInfo>’
class StringObserver: public AbstractSubject<StringInfo> {
^
In file included from headers/StringObserver.hxx:4:0,
from src/StringObserver.cpp:1:
headers/AbstractObserver.hxx:8:7: error: declaration of ‘class AbstractSubject<StringInfo>’
class AbstractSubject;
^
In file included from src/StringObserver.cpp:1:0:
headers/StringObserver.hxx:10:23: error: ‘StringSubject’ has not been declared
virtual void update(StringSubject* s);
^
headers/StringObserver.hxx:13:3: error: ‘StringSubject’ does not name a type
StringSubject* _subject;
^
src/StringObserver.cpp:9:29: error: variable or field ‘update’ declared void
void StringObserver::update(StringSubject* s) {
^
src/StringObserver.cpp:9:29: error: ‘StringSubject’ was not declared in this scope
src/StringObserver.cpp:9:44: error: ‘s’ was not declared in this scope
void StringObserver::update(StringSubject* s) {
^
Makefile:349: recipe for target 'obj/StringObserver.o' failed
make: *** [obj/StringObserver.o] Error 1
Maintenant ça marche mieux, mais encore et toujours un problème:
Quand j'ai un objet de type StringSubject, donc qui hérite de AbstractSubject<StringInfo>, et que j'appelle la méthode virtuelle, définie dans AbstractSubject, addObserver(AbstractObserver* o), en passant comme paramètre un StringObserver - donc héritant de AbstractOBserver - , le compilateur me dit que la classe StringSubject n'a pas telle méthode... Comment faire? parce que là finalement je perds tout l’intérêt du polymorphisme...
src/main.cpp: In function ‘int main()’:
src/main.cpp:17:22: error: no matching function for call to ‘StringSubject::addObserver(StringObserver*&)’
sub->addObserver(obs);
^
src/main.cpp:17:22: note: candidate is:
In file included from src/main.cpp:3:0:
headers/AbstractSubject.hxx:45:6: note: void AbstractSubject<I>::addObserver(AbstractObserver<Info>*) [with Info = StringInfo]
void AbstractSubject<Info>::addObserver(AbstractObserver<Info>* o) {
^
headers/AbstractSubject.hxx:45:6: note: no known conversion for argument 1 from ‘StringObserver*’ to ‘AbstractObserver<StringInfo>*’
Makefile:339: recipe for target 'obj/main.o' failed
make: *** [obj/main.o] Error 1
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
* Un wrapper C++ pour sqlite * Une alternative a boost units
En C++ une class est une struct où tout est privé par défaut, et une struct est une class où tout est public par défaut.
* Un wrapper C++ pour sqlite * Une alternative a boost units
* Un wrapper C++ pour sqlite * Une alternative a boost units
* Un wrapper C++ pour sqlite * Une alternative a boost units
* Un wrapper C++ pour sqlite * Une alternative a boost units
* Un wrapper C++ pour sqlite * Une alternative a boost units