Le terme «interface» en C ++

11

Java fait une distinction claire entre classet interface. (Je crois que C # fait aussi, mais je n'ai aucune expérience avec cela). Cependant, lors de l'écriture de C ++, il n'y a pas de distinction imposée par le langage entre la classe et l'interface.

Par conséquent, j'ai toujours considéré l'interface comme une solution de contournement pour le manque d'héritage multiple en Java. Faire une telle distinction semble arbitraire et dénué de sens en C ++.

J'ai toujours eu tendance à adopter l'approche "écrire les choses de la manière la plus évidente", donc si en C ++ j'ai ce qu'on pourrait appeler une interface en Java, par exemple:

class Foo {
public:
  virtual void doStuff() = 0;
  ~Foo() = 0;
};

et j'ai alors décidé que la plupart des implémenteurs de Foovoulaient partager des fonctionnalités communes que j'écrirais probablement:

class Foo {
public:
  virtual void doStuff() = 0;
  ~Foo() {}
protected:
  // If it needs this to do its thing:
  int internalHelperThing(int);
  // Or if it doesn't need the this pointer:
  static int someOtherHelper(int);
};

Ce qui fait que ce n'est plus une interface au sens de Java.

Au lieu de cela, C ++ a deux concepts importants, liés au même problème d'héritage sous-jacent:

  1. virtual héritage
  2. Les classes sans variable membre ne peuvent occuper aucun espace supplémentaire lorsqu'elles sont utilisées comme base

    "Les sous-objets de la classe de base peuvent avoir une taille nulle"

    Référence

De ceux que j'essaie d'éviter le n ° 1 dans la mesure du possible - il est rare de rencontrer un scénario où c'est vraiment la conception la plus "propre". # 2 est cependant une différence subtile mais importante entre ma compréhension du terme "interface" et les fonctionnalités du langage C ++. À la suite de cela, je ne me réfère actuellement (presque) jamais aux choses comme des "interfaces" en C ++ et je parle en termes de classes de base et de leurs tailles. Je dirais que dans le contexte de C ++ "interface" est un terme impropre.

Il a cependant été porté à mon attention que peu de gens font une telle distinction.

  1. Dois-je perdre quelque chose en autorisant (par exemple protected) des non- virtualfonctions à exister dans une "interface" en C ++? (Mon sentiment est exactement le contraire - un emplacement plus naturel pour le code partagé)
  2. Le terme «interface» a-t-il un sens en C ++ - cela signifie-t-il uniquement pur virtualou serait-il juste d'appeler une classe C ++ sans variable membre une interface?
Flexo
la source
C # a le même héritage multiple d'interfaces, l'héritage unique d'implémentation que Java, mais grâce à l'utilisation de méthodes d'extension génériques , le internalHelperThingpeut être simulé dans presque tous les cas.
Sjoerd
Notez que c'est une mauvaise pratique d'exposer des membres virtuels.
Klaim
un public ~Foo() {}dans une classe abstraite est une erreur dans (presque) toutes les circonstances.
Wolf

Réponses:

11

En C ++, le terme "interface" n'a pas qu'une seule définition largement acceptée - donc chaque fois que vous allez l'utiliser, vous devez dire ce que vous voulez dire exactement - une classe de base virtuelle avec ou sans implémentations par défaut, un fichier d'en-tête, les membres publics d'une classe arbitraire et ainsi de suite.

Concernant votre exemple: en Java (et similaire en C #), votre code impliquerait probablement une séparation des préoccupations:

interface IFoo {/*  ... */} // here is your interface

class FooBase implements IFoo 
{
     // make default implementations for interface methods
}

class Foo extends FooBase
{
}

En C ++, vous pouvez le faire mais vous n'êtes pas obligé de le faire. Et si vous aimez appeler une classe une interface si elle n'a pas de variables membres, mais contient des implémentations par défaut pour certaines méthodes, faites-le, mais assurez-vous que toutes les personnes à qui vous parlez savent ce que vous voulez dire.

Doc Brown
la source
3
Un autre sens possible de "interface", pour un programmeur C ++, est les publicparties du .hfichier.
David Thornley
Quelle langue est votre exemple de code? Si c'est une tentative de devenir C ++, je suis sur le point de pleurer ...
Qix - MONICA A ÉTÉ MISTRÉE
@Qix: restez tranquille, relisez mon message (il indique clairement que le code est en Java), et si vous avez> 2000 points, vous pouvez modifier mon message pour ajouter l'exemple de code C ++ équivalent, pour rendre mon exemple plus clair.
Doc Brown
Si c'est Java, alors c'est toujours faux, et non je ne peux pas le modifier; pas assez de caractères pour changer. Java n'utilise pas de deux-points dans l'implémentation / l'extension, c'est pourquoi je me demandais si c'était une tentative de C ++ ...
Qix - MONICA A ÉTÉ MISTRÉE
@Qix: si vous trouvez plus de problèmes de syntaxe, vous pouvez les garder comme cadeau de Noël :-)
Doc Brown
7

Cela semble un peu comme si vous étiez tombé dans le piège de confondre le sens de ce que c'est qu'une interface est conceptuellement à la fois une implémentation (l'interface - minuscule «i») et une abstraction (une interface - majuscule «I» ).

En ce qui concerne vos exemples, votre premier bit de code est simplement une classe. Bien que votre classe ait une interface dans le sens où elle fournit des méthodes pour permettre l'accès à son comportement, ce n'est pas une interface dans le sens d'une déclaration d'interface qui fournit une couche d'abstraction représentant un type de comportement auquel vous pouvez souhaiter que les classes mettre en place. La réponse de Doc Brown à votre message vous montre exactement de quoi je parle ici.

Les interfaces sont souvent présentées comme la «solution de rechange» pour les langues qui ne prennent pas en charge l'héritage multiple, mais je pense que c'est plus une idée fausse qu'une dure vérité (et je soupçonne que je pourrais être critiqué pour avoir déclaré cela!). Les interfaces n'ont vraiment rien à voir avec l'héritage multiple dans la mesure où elles ne nécessitent pas d'être héritées ou d'être un ancêtre afin de fournir une compatibilité fonctionnelle entre les classes ou une abstraction d'implémentation pour les classes. En fait, ils peuvent vous permettre de supprimer complètement l'héritage si vous souhaitez implémenter votre code de cette manière - pas que je le recommande entièrement, mais je dis simplement que vous pourriezfais le. Donc, la réalité est que, indépendamment des questions d'héritage, les interfaces fournissent un moyen par lequel les types de classe peuvent être définis, établissant les règles qui déterminent comment les objets peuvent communiquer entre eux, ce qui vous permet de déterminer le comportement que vos classes devraient prendre en charge sans dictant la méthode spécifique utilisée pour implémenter ce comportement.

Dois-je perdre quoi que ce soit en autorisant (par exemple, protégé) des fonctions non virtuelles à exister au sein d'une "interface" en C ++? (Mon sentiment est exactement le contraire - un emplacement plus naturel pour le code partagé)

Une interface pure est censée être entièrement abstraite, car elle permet de définir un contrat de compatibilité entre des classes qui n'ont pas nécessairement hérité de leur comportement d'un ancêtre commun. Une fois implémenté, vous souhaitez choisir d'autoriser ou non l'extension du comportement d'implémentation dans une sous-classe. Si vos méthodes ne le sont pas virtual, vous perdez la possibilité d'étendre ce comportement ultérieurement si vous décidez de créer des classes descendantes. Que l'implémentation soit virtuelle ou non, l'Interface définit la compatibilité comportementale en elle-même, tandis que la classe fournit l'implémentation de ce comportement pour les instances que la classe représente.

Le terme "interface" a-t-il un sens en C ++ - cela implique-t-il uniquement du virtuel pur ou serait-il juste d'appeler une classe C ++ sans variable membre une interface?

Pardonnez-moi, mais cela fait longtemps que je n'ai pas vraiment écrit une application sérieuse en C ++. Je me souviens que Interface est un mot-clé aux fins d'abstraction comme je les ai décrites ici. Je n'appellerais pas les classes C ++ d'aucune sorte une interface, je dirais plutôt que la classe a une interface dans les significations que j'ai décrites ci-dessus. En ce sens, le terme EST significatif, mais cela dépend vraiment du contexte.

S.Robins
la source
1
+1 pour la distinction entre "avoir" et "être" une interface.
Doc Brown
6

Les interfaces Java ne sont pas une «solution de contournement», elles sont une décision délibérée de conception pour éviter certains des problèmes d'héritage multiple comme l'héritage diamant et pour encourager les pratiques de conception qui minimisent le couplage.

Votre interface avec les méthodes protégées est un cas classique de besoin de «préférer la composition à l'héritage». Pour citer Sutter et Alexandrescu dans la section de ce nom de leurs excellentes normes de codage C ++ :

Évitez les droits de succession: l'héritage est la deuxième relation de couplage la plus étroite en C ++, juste après l'amitié. Un couplage serré n'est pas souhaitable et doit être évité dans la mesure du possible. Par conséquent, préférez la composition à l'héritage, sauf si vous savez que cette dernière profite vraiment à votre conception.

En incluant vos fonctions d'assistance dans votre interface, vous économisez peut-être un peu la frappe maintenant, mais vous introduisez un couplage qui vous blessera sur la route. Il est presque toujours préférable, à long terme, de séparer vos fonctions d’assistant et de les transmettre Foo*.

La STL en est un assez bon exemple. Autant de fonctions d'assistance que possible sont extraites dans au <algorithm>lieu d'être dans les classes de conteneur. Par exemple, puisque sort()utilise l'API de conteneur public pour faire son travail, vous savez que vous pouvez implémenter votre propre algorithme de tri sans avoir à changer de code STL. Cette conception permet des bibliothèques comme boost, qui améliorent la STL au lieu de la remplacer, sans que la STL ait besoin de savoir quoi que ce soit sur la boost.

Karl Bielefeldt
la source
2

Dois-je perdre quoi que ce soit en autorisant (par exemple, protégé) des fonctions non virtuelles à exister au sein d'une "interface" en C ++? (Mon sentiment est exactement le contraire - un emplacement plus naturel pour le code partagé)

On pourrait penser cela, mais placer une méthode protégée non virtuelle dans une classe par ailleurs abstraite dicte l'implémentation à quiconque écrit une sous-classe. Cela va à l'encontre du but d'une interface dans son sens pur, qui est de fournir un placage qui cache ce qui se trouve en dessous.

C'est l'un de ces cas où il n'y a pas de réponse unique et vous devez utiliser votre expérience et votre jugement pour prendre une décision. Si vous pouvez dire avec 100% de confiance que chaque sous-classe possible de votre classe autrement entièrement virtuelle Fooaura toujours besoin d'une implémentation de la méthode protégée bar(), alors Fooc'est le bon endroit pour cela. Une fois que vous avez une sous Baz- classe qui n'en a pas besoin bar(), vous devez soit vivre avec le fait qu'il Bazaura accès au code, mais pas passer par l'exercice de réorganisation de la hiérarchie de votre classe. Le premier n'est pas une bonne pratique et le second peut prendre plus de temps que les quelques minutes qu'il aurait fallu pour organiser les choses correctement en premier lieu.

Le terme "interface" a-t-il un sens en C ++ - cela implique-t-il uniquement du virtuel pur ou serait-il juste d'appeler une classe C ++ sans variable membre une interface?

La section 10.4 du standard C ++ fait une mention passante de l'utilisation de classes abstraites pour implémenter des interfaces mais ne les définit pas formellement. Le terme est significatif dans un contexte informatique général, et toute personne compétente doit comprendre que " Fooest une interface pour (quoi que ce soit)" implique une certaine forme d'abstraction. Ceux qui sont exposés à des langages avec des constructions d'interface définies peuvent penser que le virtuel est pur, mais toute personne ayant réellement besoin de travailler avec Foosa vision avant de continuer.

Blrfl
la source
2
Quelle est la différence entre fournir une déclaration virtuelle pure et une déclaration plus implémentation? Dans les deux cas, il doit y avoir une implémentation et bazpeut toujours avoir une implémentation similaire return false;ou autre. Si la méthode ne s'applique pas à toutes les sous-classes, elle n'appartient à la classe abstraite de base sous aucune forme.
David Thornley
1
Je pense que votre dernière phrase dit que nous sommes sur la même longueur d'onde: il n'y a aucune raison pour que vous ne puissiez pas avoir de méthodes protégées dans les classes abstraites, mais elles ne devraient pas être plus hautes dans l'arbre d'héritage qu'absolument nécessaires. Je pense simplement que si une classe doit simuler une implémentation, elle n'est pas au bon endroit dans l'arborescence.
Blrfl