Der Begriff "Schnittstelle" in C ++

11

Java unterscheidet klar zwischen classund interface. (Ich glaube, C # auch, aber ich habe keine Erfahrung damit). Beim Schreiben von C ++ gibt es jedoch keine sprachlich erzwungene Unterscheidung zwischen Klasse und Schnittstelle.

Folglich habe ich die Benutzeroberfläche immer als Problemumgehung für das Fehlen einer Mehrfachvererbung in Java angesehen. Eine solche Unterscheidung ist in C ++ willkürlich und bedeutungslos.

Ich habe mich immer für den Ansatz "Dinge auf die offensichtlichste Weise schreiben" entschieden. Wenn ich also in C ++ eine so genannte Schnittstelle in Java habe, z.

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

und ich entschied dann, dass die meisten Implementierer von Fooeinige gemeinsame Funktionen teilen wollten, die ich wahrscheinlich schreiben würde:

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);
};

Das macht dies dann nicht mehr zu einer Schnittstelle im Java-Sinne.

Stattdessen hat C ++ zwei wichtige Konzepte, die sich auf dasselbe zugrunde liegende Vererbungsproblem beziehen:

  1. virtual Erbschaft
  2. Klassen ohne Mitgliedsvariablen können keinen zusätzlichen Speicherplatz belegen, wenn sie als Basis verwendet werden

    "Unterobjekte der Basisklasse können eine Größe von Null haben"

    Referenz

Von denen versuche ich, Nummer 1 zu vermeiden, wo immer dies möglich ist - es kommt selten vor, dass dies wirklich das "sauberste" Design ist. # 2 ist jedoch ein subtiler, aber wichtiger Unterschied zwischen meinem Verständnis des Begriffs "Schnittstelle" und den C ++ - Sprachfunktionen. Infolgedessen bezeichne ich Dinge derzeit (fast) nie als "Schnittstellen" in C ++ und spreche in Bezug auf Basisklassen und deren Größen. Ich würde sagen, dass im Kontext von C ++ "Schnittstelle" eine Fehlbezeichnung ist.

Es ist mir jedoch aufgefallen, dass nicht viele Menschen einen solchen Unterschied machen.

  1. Kann ich irgendetwas verlieren, indem ich zulasse, dass (z. B. protected) Nichtfunktionen virtualinnerhalb einer "Schnittstelle" in C ++ existieren? (Mein Gefühl ist genau das Gegenteil - ein natürlicherer Ort für gemeinsam genutzten Code)
  2. Ist der Begriff "Schnittstelle" in C ++ sinnvoll - impliziert er nur reine virtualoder wäre es fair, C ++ - Klassen ohne Mitgliedsvariablen noch als Schnittstelle zu bezeichnen?
Flexo
quelle
C # hat die gleiche Mehrfachvererbung von Schnittstellen, einfache Vererbung der Implementierung als Java, sondern durch den Einsatz von Generika Erweiterungsmethoden , die internalHelperThingin fast allen Fällen simuliert werden.
Sjoerd
Beachten Sie, dass es eine schlechte Praxis ist, virtuelle Mitglieder verfügbar zu machen.
Klaim
Ein Publikum ~Foo() {}in einer abstrakten Klasse ist unter (fast) allen Umständen ein Fehler.
Wolf

Antworten:

11

In C ++ hat der Begriff "Schnittstelle" nicht nur eine allgemein akzeptierte Definition. Wenn Sie ihn also verwenden möchten, sollten Sie genau sagen, was Sie meinen - eine virtuelle Basisklasse mit oder ohne Standardimplementierungen, eine Header-Datei, die öffentlichen Mitglieder einer beliebigen Klasse und so weiter.

In Bezug auf Ihr Beispiel: In Java (und ähnlich in C #) würde Ihr Code wahrscheinlich eine Trennung der Bedenken bedeuten:

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

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

class Foo extends FooBase
{
}

In C ++ können Sie dies tun, sind aber nicht dazu gezwungen. Wenn Sie eine Klasse als Schnittstelle bezeichnen möchten, wenn sie keine Mitgliedsvariablen enthält, aber Standardimplementierungen für einige Methoden enthält, tun Sie dies einfach, aber stellen Sie sicher, dass jeder, mit dem Sie sprechen, weiß, was Sie meinen.

Doc Brown
quelle
3
Eine andere mögliche Bedeutung von "Schnittstelle" für einen C ++ - Programmierer sind die publicTeile der .hDatei.
David Thornley
Welche Sprache ist Ihr Codebeispiel? Wenn das ein Versuch ist, C ++ zu sein, werde ich gleich weinen ...
Qix - MONICA WURDE AM
@Qix: Halten Sie es einfach, lesen Sie mein Posting erneut (es besagt eindeutig, dass der Code in Java ist), und falls Sie> 2000 Punkte haben, können Sie mein Posting bearbeiten, um den entsprechenden C ++ - Beispielcode hinzuzufügen und mein Beispiel zu verbessern klar.
Doc Brown
Wenn das Java ist, dann ist es immer noch falsch und nein, ich kann es nicht bearbeiten. nicht genug Zeichen, um sich zu ändern. Java verwendet keine Doppelpunkte beim Implementieren / Erweitern, weshalb ich mich gefragt habe, ob es ein Versuch von C ++ war ...
Qix - MONICA WURDE AM
@ Qix: Wenn Sie mehr syntaktische Probleme finden, können Sie sie als Weihnachtsgeschenk behalten :-)
Doc Brown
7

Es hört sich ein wenig so an, als wären Sie in die Falle geraten, die Bedeutung dessen zu verwechseln, was es bedeutet, dass eine Schnittstelle konzeptionell sowohl eine Implementierung (die Schnittstelle - Kleinbuchstabe 'i') als auch eine Abstraktion (eine Schnittstelle - Großbuchstaben 'I') ist. ).

In Bezug auf Ihre Beispiele ist Ihr erstes Codebit lediglich eine Klasse. Während Ihre Klasse über eine Schnittstelle in dem Sinne verfügt, dass sie Methoden zum Ermöglichen des Zugriffs auf ihr Verhalten bereitstellt, handelt es sich nicht um eine Schnittstelle im Sinne einer Schnittstellendeklaration, die eine Abstraktionsebene bereitstellt, die einen Verhaltenstyp darstellt, den Klassen möglicherweise möchten implementieren. Die Antwort von Doc Brown auf Ihren Beitrag zeigt Ihnen genau, wovon ich hier spreche.

Schnittstellen werden oft als "Workaround" für Sprachen angepriesen, die keine Mehrfachvererbung unterstützen, aber ich bin der Meinung, dass dies eher ein Missverständnis als eine harte Wahrheit ist (und ich vermute, dass ich dafür in Flammen stehen kann!). Schnittstellen haben wirklich nichts mit Mehrfachvererbung zu tun, da sie weder vererbt noch Vorfahr sein müssen, um funktionale Kompatibilität zwischen Klassen oder Implementierungsabstraktion für Klassen zu gewährleisten. In der Tat können sie es Ihnen ermöglichen, die Vererbung effektiv vollständig zu beseitigen, wenn Sie Ihren Code auf diese Weise implementieren möchten - nicht, dass ich ihn vollständig empfehlen würde, aber ich sage nur, dass Sie dies könntenTu es. Die Realität ist also, dass Schnittstellen unabhängig von Vererbungsfragen ein Mittel bieten, mit dem Klassentypen definiert werden können, und die Regeln festlegen, die bestimmen, wie Objekte miteinander kommunizieren dürfen, sodass Sie das Verhalten bestimmen können, ohne das Ihre Klassen unterstützen sollten Festlegen der spezifischen Methode zur Implementierung dieses Verhaltens.

Kann ich etwas verlieren, wenn (z. B. geschützte) nicht virtuelle Funktionen in einer "Schnittstelle" in C ++ vorhanden sind? (Mein Gefühl ist genau das Gegenteil - ein natürlicherer Ort für gemeinsam genutzten Code)

Eine reine Schnittstelle soll vollständig abstrakt sein, da sie der Definition einen Kompatibilitätsvertrag zwischen Klassen ermöglicht, die ihr Verhalten möglicherweise nicht unbedingt von einem gemeinsamen Vorfahren geerbt haben. Bei der Implementierung möchten Sie auswählen, ob das Implementierungsverhalten in einer Unterklasse erweitert werden soll. Wenn dies nicht der Fall ist virtual, können Sie dieses Verhalten später nicht mehr erweitern, wenn Sie entscheiden, dass Sie Nachkommenklassen erstellen müssen. Unabhängig davon, ob die Implementierung virtuell ist oder nicht, definiert die Schnittstelle die Verhaltenskompatibilität an und für sich, während die Klasse die Implementierung dieses Verhaltens für die Instanzen bereitstellt, die die Klasse darstellt.

Ist der Begriff "Schnittstelle" in C ++ sinnvoll - impliziert er nur rein virtuell oder wäre es fair, C ++ - Klassen ohne Mitgliedsvariablen noch als Schnittstelle zu bezeichnen?

Verzeih mir, aber es ist lange her, dass ich wirklich eine ernsthafte Anwendung in C ++ geschrieben habe. Ich erinnere mich, dass Interface ein Schlüsselwort für die Zwecke der Abstraktion ist, wie ich sie hier beschrieben habe. Ich würde C ++ - Klassen keiner Art als Schnittstelle bezeichnen, sondern stattdessen sagen, dass die Klasse eine Schnittstelle mit den oben beschriebenen Bedeutungen hat. In diesem Sinne ist der Begriff bedeutungsvoll, aber das hängt wirklich vom Kontext ab.

S.Robins
quelle
1
+1 für die Unterscheidung zwischen "Haben" und "Sein" einer Schnittstelle.
Doc Brown
6

Java-Schnittstellen sind keine "Problemumgehung", sondern eine bewusste Entwurfsentscheidung, um einige der Probleme bei der Mehrfachvererbung wie der Diamantvererbung zu vermeiden und Entwurfspraktiken zu fördern, die die Kopplung minimieren.

Ihre Schnittstelle mit geschützten Methoden ist ein Lehrbuchfall, bei dem Sie "Komposition gegenüber Vererbung bevorzugen" müssen. Um aus Sutter und Alexandrescu in dem Abschnitt mit diesem Namen aus ihren hervorragenden C ++ - Codierungsstandards zu zitieren :

Vermeiden Sie Erbschaftssteuern: Vererbung ist nach Freundschaft die zweit engste Kopplungsbeziehung in C ++. Eine enge Kupplung ist unerwünscht und sollte nach Möglichkeit vermieden werden. Ziehen Sie daher die Komposition der Vererbung vor, es sei denn, Sie wissen, dass letztere Ihrem Design wirklich zugute kommt.

Indem Sie Ihre Hilfsfunktionen in Ihre Benutzeroberfläche aufnehmen, sparen Sie möglicherweise ein wenig Tipparbeit, aber Sie führen eine Kopplung ein, die Sie später verletzen wird. Langfristig ist es fast immer besser, die Funktionen Ihres Helfers zu trennen und zu übergeben Foo*.

Die STL ist ein ziemlich gutes Beispiel dafür. Es werden so viele Hilfsfunktionen wie möglich herausgezogen, <algorithm>anstatt in den Containerklassen zu sein. Da Sie sort()beispielsweise die öffentliche Container-API für ihre Arbeit verwenden, wissen Sie, dass Sie Ihren eigenen Sortieralgorithmus implementieren können, ohne STL-Code ändern zu müssen. Dieses Design ermöglicht Bibliotheken wie Boost, die die STL verbessern, anstatt sie zu ersetzen, ohne dass die STL etwas über Boost wissen muss.

Karl Bielefeldt
quelle
2

Kann ich etwas verlieren, wenn (z. B. geschützte) nicht virtuelle Funktionen in einer "Schnittstelle" in C ++ vorhanden sind? (Mein Gefühl ist genau das Gegenteil - ein natürlicherer Ort für gemeinsam genutzten Code)

Sie würden das denken, aber das Einfügen einer geschützten, nicht virtuellen Methode in eine ansonsten abstrakte Klasse diktiert die Implementierung für jeden, der eine Unterklasse schreibt. Dadurch wird der Zweck einer Schnittstelle im reinen Sinne zunichte gemacht, nämlich ein Furnier bereitzustellen, das die darunter liegenden Elemente verbirgt.

Dies ist einer der Fälle, in denen es keine einheitliche Antwort gibt und Sie Ihre Erfahrung und Ihr Urteilsvermögen einsetzen müssen, um eine Entscheidung zu treffen. Wenn Sie mit 100% iger Sicherheit sagen können, dass jede mögliche Unterklasse Ihrer ansonsten vollständig virtuellen Klasse Fooimmer eine Implementierung der geschützten Methode benötigt bar(), dann Fooist dies der richtige Ort dafür. Sobald Sie eine Unterklasse haben Baz, die Sie nicht benötigen bar(), müssen Sie entweder mit der Tatsache leben, dass Sie BazZugriff auf Code haben, den Sie nicht benötigen , oder Sie müssen Ihre Klassenhierarchie neu ordnen. Ersteres ist keine gute Übung, und letzteres kann länger dauern als die wenigen Minuten, die nötig gewesen wären, um die Dinge überhaupt richtig zu arrangieren.

Ist der Begriff "Schnittstelle" in C ++ sinnvoll - impliziert er nur rein virtuell oder wäre es fair, C ++ - Klassen ohne Mitgliedsvariablen noch als Schnittstelle zu bezeichnen?

In Abschnitt 10.4 des C ++ - Standards wird die Verwendung abstrakter Klassen zur Implementierung von Schnittstellen vorübergehend erwähnt, aber nicht formal definiert. Der Begriff ist in einem allgemeinen Informatikkontext von Bedeutung, und jeder, der kompetent ist, sollte verstehen, dass " Fooeine Schnittstelle für (was auch immer) ist" eine Form der Abstraktion impliziert. Diejenigen, die mit Sprachen mit definierten Schnittstellenkonstrukten vertraut sind, denken vielleicht rein virtuell, aber jeder, der tatsächlich damit arbeiten muss, Foowird sich die Definition ansehen, bevor er fortfährt.

Blrfl
quelle
2
Was ist der Unterschied zwischen der Bereitstellung einer reinen virtuellen Deklaration und einer Deklaration plus Implementierung? In beiden Fällen muss es eine Implementierung geben, und es bazkann immer eine Implementierung wie return false;oder was auch immer geben. Wenn die Methode nicht für alle Unterklassen gilt, gehört sie in keiner Form zur abstrakten Basisklasse.
David Thornley
1
Ich denke, Ihr letzter Satz besagt, dass wir uns auf derselben Seite befinden: Es gibt keinen Grund, warum Sie in abstrakten Klassen keine geschützten Methoden haben können, aber sie sollten im Vererbungsbaum nicht höher sein als unbedingt erforderlich. Ich denke nur, wenn eine Klasse eine Implementierung dumm machen muss, ist sie nicht an der richtigen Stelle im Baum.
Blrfl