Comment déclarer une interface en C++

Giles314 Messages postés 21 Date d'inscription samedi 23 décembre 2006 Statut Membre Dernière intervention 23 avril 2021 - 20 déc. 2007 à 11:25
Giles314 Messages postés 21 Date d'inscription samedi 23 décembre 2006 Statut Membre Dernière intervention 23 avril 2021 - 23 déc. 2007 à 02:11
Bonjour,

Ma question est quelque peu théorique car j'ai une solution mais qui ne me semble pas "pure".
Ce que j'appelle une interface est une spécification sans code associé (virtuelle pure) par exemple
class Interface
{
    virtual int Run (void) = 0;
    virtual ~Interface ()  = 0;
};

Malheureusement une classe comme celle-ci ne se linke pas dans mon environnement gcc sous MinGW (mais je suppose que c'est vrai avec d'autres compilateurs).

En effet si je déclare:
class Implementation : public Interface
{
    Implementation();
    virtual int Run (void);
    virtual ~Implementation ();
};
int main()
{
    Implementation Programme;
    return Programme.Run();
}

Toute utilisation de Implementation va générer une référence à la fonction Interface::~Interface qui n'existe pas.
Y a-t-il une solution à ce problème sans définir le destructeur (virtual ~Interface (){}) qui mettrait du code dans l'interface - certe pas beaucoup - qui perdrait ainsi son status d'interface pure ?
Je sais je suis un peu tatillon, mais une interface c'est juste une interface

40 réponses

luhtor Messages postés 2023 Date d'inscription mardi 24 septembre 2002 Statut Membre Dernière intervention 28 juillet 2008 6
22 déc. 2007 à 11:48
Dans un post précédent, tu affirmes toi meme:
""détruire une interface n'a pas de sens et ne résulte que d'une mauvaise conception." Je suis tout à fait d'accord."

Et pourtant, c'est exactement ce que tu fais:
    Interface *Programme;
    Programme = Create(False);
    Programme->methode();
    delete Programme;

Je te renvois la ballon en disant, c'est toi qui a mal compris la définition d'une interface. Dans l'exemple que tu donnes a la fin, c'est de l'héritage que tu fais (et que tu dois faire) et tu utilises la classe Interface comme une classe base à l'héritage. MAIS en aucun cas, ta classe Interface n'en est vraiment une.

Voici un exemple schématique du role de l'interface.
class Affichable // Notre interface !
{
virtual void Draw() = ;
};

class BaseObject
{
public:
virtual ~BaseObject();
}

class ObjectUnPeuPlusEvolue : public BaseObject, public Affichable
{
public:
virtual ~ObjectUnPeuPlusEvolue();
}

Et maintenant, on peut avoir un manager d'objets qui créé et détruit les objets dérivés de BaseObject avec notamment une factory pourquoi pas. A coté, on peut avoir un manager dédié aux objets qui peuvent etre affichés (il manipulera des objets Affichage*), mais en aucun cas se manager ne sera capable de créer ou détruire des objets. Il ne sera donc jamais amené à détruire des Interfaces.

Donc en conclusion, soit certain de bien comprendre la nuance entre Héritage et Interface. Je dirais que d'une facon général, une classe qui utilise une Interface hérite toujours D'ABORD d'une classe de base. (en général)
0
luhtor Messages postés 2023 Date d'inscription mardi 24 septembre 2002 Statut Membre Dernière intervention 28 juillet 2008 6
22 déc. 2007 à 11:50
Bon tu auras remarqué, que j'ai oublié d'implémenter la fonction "Draw" dans la classe ObjectUnPeuPlusEvolue.
0
Giles314 Messages postés 21 Date d'inscription samedi 23 décembre 2006 Statut Membre Dernière intervention 23 avril 2021
22 déc. 2007 à 12:27
Je vois pas pourquoi tu n'es pas d'accord sur le fait que ma classe n'ayant pas de code (à l'exception de ce foutu destructeur dont je cherche à virer le code) soit une interface. Elle constitue bien une spec, qui ne préjuge en rien de l'implémentation et que je peux appeller sans connaître cette implémentation. Ce qui est selon moi le rôle d'une interface.

Quand tu dis que
    Interface *Programme;
    Programme = Create(False);
    Programme->methode();
    delete Programme;
c'est détruire une interface je ne suis pas d'accord. Ce qui est détruit c'est l'implémentation de l'interface fournie par Create. En effet l'interface étant virtuelle pure elle n'a pas d'existance concrète. Par contre l'implémentation est tout à fait concrète et a besoin que son destructeur soit appelé. Mais le main ne peut pas appeler ~Implementation1 ou ~Implementation2 puisqu'il ne les connaît pas. Il appelle donc ~Interface qui par polymorphisme va appeller le destructeur de la bonne classe. C'est ce qui ne marche pas dans ton exemple.

En effet si un programme ne connaît que l'interface Affichage et qu'il a besoin de détruire une implémentation d'Affichage qui lui aura été donnée, il est incapable de le faire puisque Affichage n'a pas de méthode de destruction. ~BaseObject et ~ObjectUnPeuPlusEvolue ne seront donc jamais appelées par un programme qui n'a pas visibilité sur ObjectUnPeuPlusEvolue. Et si l'utilisateur a besoin de voir ObjectUnPeuPlusEvolue, c'est que l'interface ne fait pas son rôle d'isoler l'utilisation de l'implémentation.  Ce n'est donc pas une bonne interface.

En fait je me demande tout d'un coup quelle est donc ta définition d'une interface ?
0
luhtor Messages postés 2023 Date d'inscription mardi 24 septembre 2002 Statut Membre Dernière intervention 28 juillet 2008 6
22 déc. 2007 à 13:19
"

Je vois pas pourquoi tu n'es pas d'accord sur le fait que ma classe n'ayant pas de code"
=> Je suis pas d'accord car c'est cette classe que tu manipules, alors qu'une interface ne fournit qu'une définition de fonctions et pas d'objets. Hors toi tu créés et détruis des objets de ta pseudo interface.

"En effet si un programme ne connaît que l'interface Affichage et qu'il
a besoin de détruire une implémentation d'Affichage qui lui aura été
donnée, il est incapable de le faire puisque Affichage n'a pas de
méthode de destruction."
=> MAIS justement, il n'a pas a le faire ! C'est ce que je me tue à te dire depuis le début lol. Si une telle chose arrive, c'est une erreur de conception et rien d'autre.

"Et si l'utilisateur a besoin de voir ObjectUnPeuPlusEvolue, c'est que
l'interface ne fait pas son rôle d'isoler l'utilisation de
l'implémentation.  Ce n'est donc pas une bonne interface."
=> L'interface a pour but de décire une catégorie d'objets possédant certaines fonctions supplémentaires. Tous les objets qui souhaitent fournir ces fonctions supplémentaires doivent utiliser l'interface en question. C'est tout.

Mais dans ton cas a toi, si tu veux faire des systèmes de Factory, ce n'est plus des Interfaces mais des classes.

class Object_Enregistrable
{
virtual void Read(file &) = 0;
virtual void Write(file &) = 0;

virtual ~Object_Enregistrable() {}
};

Dans la suite, la factory ne manipule que des "Object_Enregistrable" mais qui n'est pas une interface.
   Object_Enregistrable *Programme = FactoryCreate();
   delete Programme;

Il ne s'agit plus d'une interface. Objet_Enregistrable devient la classe de base puisque tu en créés et détruis des instances.

Une interface c'est simple: Ni constructeur/ni destructeur. Une interface ne décrit rien, juste un aspect fonctionnel de l'objet, c'est à dire en pratique, une série de fonctions disponibles.

Enfin en conclusion, faut bien qu'on achève ce post un jour. Fait comme tu veux :) Sachant que dès que tu vas mettre un destructeur dans une interface, l'interface en question devient une classe.
0

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
Giles314 Messages postés 21 Date d'inscription samedi 23 décembre 2006 Statut Membre Dernière intervention 23 avril 2021
22 déc. 2007 à 14:10
Je crois effectivement que c'est sur la définition de l'interface qu'il y a une différence. Pour toi je comprends qu'une interface est un pur concept qui n'a aucun intéret à être utilisé ou codé en C++.

Pour moi c'est une specification (sans code) dont on ne connaît pas l'implémentation qui permet d'isoler l'utilisation et l'implémentation ce qui est quelque chose de crucial selon moi en programation objet.
Et pour avoir cette utilité en C++ elle doit possèder un destructeur, car la destruction d'un objet est une des fonctions de l'implémentation que l'utilisateur de l'interface a besoin.
En effet il existe le concept de factory pour obtenir une implementation et ainsi palier l'absence de prototype constructeur dans l'interface (logique: l'objet n'existant pas encore on ne peut pas lui demander de se contruire). Par contre je ne connais pas le concept inverse qui permet de détruire une implémentation sans la connaître (et je doute qu'il existe car l'objet n'a aucune difficulté à savoir se détruire). Donc je ne vois pas pourquoi il faudrait interdire à l'interface de spécifier que l'objet a une fonction de destruction. Evidemment si le language était bien fait (comme probablement en Java) il ne demanderait même pas au programmeur de l'écrire car c'est implicite que l'implémentation de l'interface doit pouvoir être détruite. En C++ ça ne l'est pas, le destructeur doit être spécifié explicitement (ce qui ne me choque pas vraiment) et il faut même lui définir du code (c'est là que je trouve que le C++ déraille complètement car ce code ne fait rien par définition).

Je ne suis pas suffisament un puriste de la conception objet pour savoir qui a raison sur la définition "officielle", mais il me semble que ma définition a plus d'intéret pratique, et correspond à quelque chose qu'on utilise en C++. En effet je ne vois pas ce qu'on peut faire d'utile avec une interface C++ telle que tu la définis.
0
luhtor Messages postés 2023 Date d'inscription mardi 24 septembre 2002 Statut Membre Dernière intervention 28 juillet 2008 6
22 déc. 2007 à 15:36
Oui le problème vient de la définition de l'interface. Ce dont tu parles, ce n'est pas une interface au sens réel du terme. Tu la considères comme une classe basique, c'est tout.

"En effet je ne vois pas ce qu'on peut faire d'utile avec une interface C++ telle que tu la définis."
=> Dommage, car c'est très utile.

"Evidemment si le language était bien fait (comme probablement en Java)
il ne demanderait même pas au programmeur de l'écrire car c'est
implicite que l'implémentation de l'interface doit pouvoir être
détruite."
=> Bon, on est pas encord d'accord sur ce point.

Bref, la discution tourne en rond donc j'insisterai pas plus longtemps.
0
Giles314 Messages postés 21 Date d'inscription samedi 23 décembre 2006 Statut Membre Dernière intervention 23 avril 2021
22 déc. 2007 à 15:58
"En effet je ne vois pas ce qu'on peut faire d'utile avec une interface C++ telle que tu la définis."
=> Dommage, car c'est très utile.
Mais alors pourquoi tes exemples ne démontrent que le contraire ? Dans tout tes exemples on enlève ce que tu appèles l'interface et ils marchent aussi bien. A quoi peut-elle donc servir ?

Dans mon exemple par contre (celui avec Implementation1 et Implementation2) la suppression de mon interface oblige le main à connaître les implementations.
0
luhtor Messages postés 2023 Date d'inscription mardi 24 septembre 2002 Statut Membre Dernière intervention 28 juillet 2008 6
22 déc. 2007 à 18:25
"Mais alors pourquoi tes exemples ne démontrent que le contraire ?"
= > Car tu veux faire dire à mes exemples ce qu'ils ne disent pas. Pb de point de vue.

class Affichable // L'interface, une VRAI !
{
virtual void Draw() = 0;
};

class BaseObject
{
public:
virtual ~BaseObject();
}

class ObjectUnPeuPlusEvolue : public BaseObject, public Affichable // Un objet affichable
{
public:
virtual ~ObjectUnPeuPlusEvolue();
virtual void Draw();
}

class ClassQuiNaRienAVoir : public Affichable // Un Autre !
{
public:
virtual void Draw();

}

L'exemple est pourtant clair... le manager qui gère les objets affichables se fou de connaitre l'origine de l'objet tant que ce dernier fournit les fonctions nécessaires. C'est le but de l'interface. Après tu en fais ce que tu veux.
0
cs_juju12 Messages postés 966 Date d'inscription samedi 3 avril 2004 Statut Membre Dernière intervention 4 mars 2010 4
22 déc. 2007 à 18:47
Pour ma part je suis convaincu; merci luhtor.
0
Giles314 Messages postés 21 Date d'inscription samedi 23 décembre 2006 Statut Membre Dernière intervention 23 avril 2021
22 déc. 2007 à 19:03
Si j'enlève l'interface dans ton exemple est-ce que cela casse quelque chose ? Personnellement je ne vois pas. On a toujours 2 classes avec chacune une méthode Draw. Donc ton exemple ne démontre rien sauf qu'on peut faire plus compliqué en ajoutant une interface. Je n'appelle pas ça quelque chose d'utile.

class ObjectUnPeuPlusEvolue : public BaseObject  // Un objet affichable
{
public:
~ObjectUnPeuPlusEvolue();
void Draw();
}

class ClassQuiNaRienAVoir // Un Autre !
{
public:
void Draw();
}

Pour démontrer l'utilité il faudrait probablement faire un programme qui utilise ces classes et c'est là que ça ne marchera plus sans destructeur.
0
luhtor Messages postés 2023 Date d'inscription mardi 24 septembre 2002 Statut Membre Dernière intervention 28 juillet 2008 6
22 déc. 2007 à 19:19
Mais faut lire les notes en bas de mon post aussi:
"le manager qui gère les objets affichables se fou de connaitre
l'origine de l'objet tant que ce dernier fournit les fonctions
nécessaires. C'est le but de l'interface. Après tu en fais ce que tu
veux."

Jvais quand meme pas faire un code entier. Si ca t'intéresse pas ce que j'essai de dire, alors on arrete la tout de suite la discution.

class Affichable // L'interface, une VRAI !
{
virtual void Draw() = 0;
};

class BaseObject
{
public:
virtual ~BaseObject();
}

class ObjectUnPeuPlusEvolue : public BaseObject, public Affichable // Un objet affichable
{
public:
virtual ~ObjectUnPeuPlusEvolue();
virtual void Draw();
}

class ClassQuiNaRienAVoir : public Affichable // Un Autre !
{
public:
virtual void Draw();
}

class Manager_Singleton
{
public:
void AjouterObjetADessiner(Affichable*);
};

La comme ca, tu comprends ou faut que je foute les 1000 lignes ?
0
Giles314 Messages postés 21 Date d'inscription samedi 23 décembre 2006 Statut Membre Dernière intervention 23 avril 2021
22 déc. 2007 à 19:35
Non merci c'est bon. Tu as écrit suffisament de code pour que je te démontre que ça ne marche pas.
Si on suppose que ce que doit faire AjouterObjetADessiner c'est dessiner l'objet passé et ensuite le détruire, la méthode ne peut pas y arriver.
0
luhtor Messages postés 2023 Date d'inscription mardi 24 septembre 2002 Statut Membre Dernière intervention 28 juillet 2008 6
22 déc. 2007 à 19:41
Lol, car si tu te rappelles ske j'avais dis:
Ici, le Manager_Singleton n'a pas le droit de détruire des objets. Tu es tétu toi.

class Affichable // L'interface, une VRAI !
{
virtual void Draw() = 0;
};

class BaseObject
{
public:
virtual ~BaseObject();
}

class ObjectUnPeuPlusEvolue : public BaseObject, public Affichable // Un objet affichable
{
public:
virtual ~ObjectUnPeuPlusEvolue();
virtual void Draw();
}

class ClassQuiNaRienAVoir : public Affichable // Un Autre !
{
public:
virtual void Draw();
}

class Manager_Singleton // NE VA JAMAIS DETRUIRE UN OBJET
{
public:
void AjouterObjetADessiner(Affichable*);

// Evenements
void OnObjectDestroy(); // Alors on enleve l'objet en cours de destruction.
};

class THE_BIG_OBJECT_MANAGER_Singleton
{
public:
void AddObject(BaseObject *);
void DestroyAllObjects();
};

Et puis la, si tu piges toujours pas, je peux plus rien.
0
Giles314 Messages postés 21 Date d'inscription samedi 23 décembre 2006 Statut Membre Dernière intervention 23 avril 2021
22 déc. 2007 à 20:05
OK tu as démontré qu'une interface peut servir pour un singleton qui est le cas particulier dont je parlais précédemment : un des rares objets qu'on a pas à détruire.
Donc selon toi une interface ne peut être attachée qu'à un singleton ?
Personnellement j'utilisais les interfaces dans un cadre moins restrictif.

Et je vois que tu as créé l'usine à gaz dont je parlais précédemment qui est l'inverse de la factory pour détruire les objets.
0
luhtor Messages postés 2023 Date d'inscription mardi 24 septembre 2002 Statut Membre Dernière intervention 28 juillet 2008 6
22 déc. 2007 à 21:23
Oui une interface n'a d'intéret que pour etre utilisé par un Manager ou quelque chose de ce genre.

"OK tu as démontré qu'une interface peut servir pour un singleton qui
est le cas particulier dont je parlais précédemment : un des rares
objets qu'on a pas à détruire."
=> Tu as rien compris

"Personnellement j'utilisais les interfaces dans un cadre moins restrictif."
=> Forcément puisque tu n'utilises pas une interface mais une classe.

"Et je vois que tu as créé l'usine à gaz dont je parlais précédemment qui est l'inverse de la factory pour détruire les objets."
=> Donc je laisse tomber. Continue a faire ton bricolage. Je pensais que tu voulais parler "théorie" avec ton premier post, je me suis planté.
0
Giles314 Messages postés 21 Date d'inscription samedi 23 décembre 2006 Statut Membre Dernière intervention 23 avril 2021
22 déc. 2007 à 22:16
Je ne parle pas principalement théorie mais surtout codage C++ (voir le titre du forum, le titre du thread et aussi les nombreuses références que je fais au langage).

En théorie une interface n'a peut-être pas de destructeur mais ça n'est que de la théorie. Quand on en code une en C++ (qui n'a malheureusement pas bien intégré le concept d'interface et ne parvient pas à égaler la théorie), il faut en déclarer un sans quoi l'interface n'est utilisable que dans des cas très particuliers.
Or si je reviens un peu côté théorie, le but de déclarer une interface est justement de pouvoir l'utiliser dans un large éventail d'applications et de contextes, ainsi que de pouvoir en fournir un grand nombre d'implémentations variées.

Donc vouloir faire une implémentation C++ en s'attachant à la forme de la théorie (c'est à dire en ne déclarant pas de destructeur) cela conduit à un code qui ne respecte pas la raison d'être de l'interface (découplage maximal entre utilisation et implémentation).

Notre différence de définition vient donc que tu t'attaches à la forme alors que je m'attache au fond. En C++ les 2 sont incompatibles.
0
luhtor Messages postés 2023 Date d'inscription mardi 24 septembre 2002 Statut Membre Dernière intervention 28 juillet 2008 6
22 déc. 2007 à 22:55
Oui ok, mais en pratique en C++, une classe qui définit un destructeur ne peut pas etre une interface (au sens réel du terme). C'est tout, c'est simple, c'est le langage qui est comme ca et c'est très bien. C'est plus proche de la définition théorique de l'interface.
Des que tu vas mettre un destructeur dans une interface, chaque objet utilisant l'interface va contenir un pointeur vers la table des méthodes (vtable).

Mais rien ne t'empeche de faire de l'héritage multiple dans le cas de ta Factory. Faut juste savoir que tu seras confrontés au décalage du pointeur "this", ce qui ne pose pas de problème en général.
0
Giles314 Messages postés 21 Date d'inscription samedi 23 décembre 2006 Statut Membre Dernière intervention 23 avril 2021
22 déc. 2007 à 23:41
Je suis étonné par ton histoire de Vtable. En effet dans ton exemple sans destructeur tous les objets implémentant l'interface ont aussi une VTable puisqu'ils ont des méthodes virtuelles. Où est la différence ? Ou est le problème d'avoir une VTable ?

Par contre dans la mesure du possible j'évite l'héritage double. Si c'est faisable, je déplace l'une des classes de base en temps que membre de la classe dérivée et je mets dans l'interface dérivée des inline accédant aux méthodes de l'objet embarqué plus éventuellement une méthode retournant une référence sur l'objet embarqué pour avoir quelque chose à passer aux méthodes demandant cet objet.
Je ne crois pas que ce soit très accadémique mais l'implémentation est plus simple sous le debugger.
Evidemment ça ne marche pas avec des classes abstraites... Mais avec un peu de chance l'une des deux ne l'est pas.

En tout cas faire de l'héritage multiple juste pour mettre le destructeur virtuel dans la seconde classe et ne pas l'avoir dans l'interface je trouve ça bien compliqué. Entre ça et l'implémentation du destructeur de la classe interface, je préfère garder mon  implémentation du destructeur qui en plus sera éliminée en cas de compilation en mode optimisé (pas trop compliqué d'inliner un code vide ).
0
luhtor Messages postés 2023 Date d'inscription mardi 24 septembre 2002 Statut Membre Dernière intervention 28 juillet 2008 6
23 déc. 2007 à 00:43
Une vtable n'est pas un pb. Lorsque l'on fait de l'héritage multiple, un objet va alors posséder plusieurs vtable, c'est à dire 4 octets au début de ton objet. Suffit de regarder avec un bon débuggeur: genre visual. Donc le pointeur "this" se décale suivant la classe vers laquelle on cast:

class Base
{
virtual ~Base();
};

class Pseudo_Interface
{
virtual ~Pseudo_Interface();  // virtuelle ou virtuelle pure, c'est pareil.
};

class Object : public Base, public Pseudo_Interface // Héritage multiple
{
virtual ~Object();
};

int main(...)
{
Object * lObject = new Object();

Pseudo_Interface * lCopy = static_cast(lObject);

// Ici, on a:  (lCopy != lObject)
...
};

Ce décalage de pointeur peut poser d'énormes problèmes (je parle en connaissance de cause ^^). Le pointeur est décalé de 4 octets (sur ma machine), c'est à dire les 4 octets du pointeur vers la vtable de la classe Pseudo_Interface. L'objet "lObject" possède deux vtables.

Alors que dans le cas d'une interface pure, cad sans destructeur le pointeur reste le meme et on a bien à la fin (lCopy == lObject).

Enfin bref, dans ton cas, tu pourras surement faire abstraction de ces problèmes.
0
Giles314 Messages postés 21 Date d'inscription samedi 23 décembre 2006 Statut Membre Dernière intervention 23 avril 2021
23 déc. 2007 à 02:11
Le problème vient plus de l'héritage multiple que de la VTable car on obtient le même problème si Base est:


class Base
{
    ~Base();
private:
    int x;
};



Et pourtant là il n'y plus de VTable (dans Base car dans les 2 autres il en reste et celle de Object ne sera pas en début de classe).
Mais comme tu dis, je n'ai pas trop ce problème puisque j'évite l'héritage multiple. Par contre j'utilise la spécialisation, les factories et les observers à haute dose. Et je n'ai jamais eu de problème avec les VTab sauf une fois où j'ai du renoncer au polymorphisme pour pouvoir sauvegarder le contenu binaire d'une classe dans un fichier. Ca aurait été une mauvaise idée de sauvegarder un pointeur sur une VTab ! Mais de toute façon c'est très moche comme façon de faire une sérialisation (mais par contre c'est vite codé ), alors c'est vraiment pas un cas à prendre en considération.
0
Rejoignez-nous