[C++ vs. Java] Warum hat C++ keine interfaces?



  • @Shade of Mine:
    Ich Teile die Meinung, das Interfaces nicht der Weisheit letzter Schluss sein können. Und siehe da: Die ganzen ganz neuen Sprachen (die aus den Research-Projekten) verwenden alle sogenannte Traits. Das sind quasi Interfaces, wobei Methoden allerdings implementiert sein dürfen. Genial einfach, einfach genial.

    Mehrfachvererbung wird "heutzutage" durch Mixins abgelöst. Man hat die selben Vorteile ohne die Nachteile.

    -------------------

    Die gcc bot früher mal was ähnliches wie Interfaces (dort Signaturen genannt). Allerdigns musste man nicht explizit angeben, dass man eine Signatur implementiert, sondern tat das implizit, sobald man alle Methoden implementiert, die die Signatur forderte. So konnte man auch bereits vorhandene Hierarchien nach oben hin erweitern. IMHO ziemlich genial. Das Prinzip ist generell als strukturelles Subtypeing bekannt.
    Mit der 3er Version ist diese Erweiterung leider verschwunden 😞



  • Helium schrieb:

    Die gcc bot früher mal was ähnliches wie Interfaces (dort Signaturen genannt). Allerdigns musste man nicht explizit angeben, dass man eine Signatur implementiert, sondern tat das implizit, sobald man alle Methoden implementiert, die die Signatur forderte.

    So ist das bei der genannten Interface-Library ja auch.



  • Shade Of Mine schrieb:

    Optimizer schrieb:

    Was ist das geforderte Interface von std::sort? begin() und end(). Aber du hast kein Sprachmittel dafür. Das wäre in Java ein Interface.

    Nur dass man das auch mit einer abstrakten Klasse machen kann 😉

    Interface ist natürlich _aussagekräftiger_. Keine Frage.

    Und effizienter! Interface-Implementierungen müssen nicht virtuell sein, bei einer ABC ist das zumindest konzeptionell schon so. In Java ist es mit nicht-virtuell auch nicht ganz leicht, aber man kann sich dazu beispielsweise C# ansehen.

    Du bist gezwungen, die Schnittstelle, die du nutzen willst, zu spezifizieren. Mit Vererbung hat das hier an keiner Stelle was zu tun. Es ist lediglich eine Option, Teile eines Interfaces als virtuelle Methode zu implementieren, um die Implementierung redefinieren zu können.

    Nur das Problem dass ich damit habe ist, dass es manchmal verdammt vernünftige default Implementierungen gibt.

    Das ist überhaupt kein Problem. Implementierung erben -> ableiten
    Schnittstelle erfüllen -> Interface implementieren

    wieso sollte das in einem Widerspruch zueinander stehen? Selbst wenn deine ABC und dein Interface die selbe Schnittstelle haben, ist es (im Gegensatz zu manchen Situationen in C++) kein Problem. Und wenn ich die Schnittstelle schon hab und nur noch mal implements FooBarAble schreiben muss, damit die FooBarList meine Klasse frisst, ist es auch noch egal. Kein Mehraufwand und Schnittstellen werden eben über Typen spezifiziert und nicht über die Schnittstelle selbst, wie in C++. Ist vielleicht manchmal unschöner, aber mal ehrlich sind Schnittstellen, vor allem in Templates bei C++ immer so schön? Wenn der Compiler irgendwann mal merkt, dass ::basic_stream<char, myNew, sonstwas>::operator++(int) nicht existiert, ist das schöner, also wenn er dir nur sagt, dass IncrementAble nicht implementiert ist? Hat halt Vor- und Nachteile und ich finde halt die Typisierung-Lösung mit Interfaces schöner. Das kannst du anders sehen, aber sinnlos sind Interfaces sicher nicht.

    zB bei c++ der op= - er ruft den op= aller member auf. Super defaultentscheidung, sie reicht meistens aus.
    Nun hast du aufeinmal das Problem, dass IAssignable das nicht macht.
    Du musst also ganz klar trennen und damit in der Schnittstelle der Klasse festlegen, dass du das Defaultverhalten willst, indem du nicht IAssignable implementierst, sondern von AbstractAssignable erbst.

    ... womit du natürlich implizit IAssignable implementierst, den AbstractAssignable macht das ja, wovon ich ausgehe. Also ist der Unterschied folgender:

    Java: Ich überleg mir, will ich Default-Assignment, oder implementiere ich die Schnittstelle selbst?
    C++: Alle Klassen "sind von AbstractAssignable standardmäßig" abgeleitet.
    Java: Ich nehm einfach immer AbstractAssignable und redefiniere es, wenn ich will. Dann hab ich das Verhalten von C++ in dieser Hinsicht.

    Das ist in C++ jetzt nur aus einem Grund schöner, weil jede Klasse automatisch nen op= kriegt. Das gilt aber jetzt z.B. nicht mehr für getHashCode(). Da musst du auch jede Klasse explizit von AbstractHashcodeProvider ableiten.

    Aber was mich grad richtig beschäftigt, wo ist hier Codeduplizierung deswegen?

    Ich habe 1 Klasse und 1 Interface. Ich muss mich entscheiden von was ich erbe/implementiere.
    Wenn ein Interface ein Defaultverhalten haben könnte, würde das Interface reichen. Und ich müsste mich nie entscheiden was ich mache, weil ich problemlos jederzeit die methode 'assign()' dennoch überschreiben kann.

    Was ist an der Entscheidung so schwer? Du weißt doch, ob du das Default-Verhalten willst, oder nicht. 😕 Und du kannst es auch im C++-Stil machen und erben und redefinieren, was halt unnötig ist. Interfaces sind nichts, wodurch du was für deine Klasse bekommst.
    Interfaces sind Verpflichtungen, damit die sort-Methode sagen kann "Sei Comparable, sonst nehm ich dich nicht." Warum soll der Entwickler der sort-Methode jetzt ein Default-Comparable für seine Kundschaft implementieren. Und wie soll das überhaupt möglich sein?

    Wenn ich von AbstractAssignable erbe, dann kann ich quasi nicht mehr assign() überschreiben, denn dann würde ich die leute ja verwirren. was aber, wenn ich glaube, dass ich assign() später einmal überschreiben werde, aber für die erste version es nicht tue. soll ich von IAssignable erben und die 100.000 methoden so implementieren wie es AbstractAssignable macht und dadurch sinnlose code verdoppelung habe, oder doch lieber von AbstractAssignable erben und später die leute vor den kopf stossen weil ich entweder die Schnittstelle ändere indem ich direkt von IAssignable erbe oder einfach sage ich bin ein AbstractAssignable dass sich verhält als wäre es keins?

    Kannst du dazu mal ein Beispiel geben, ich kann das nicht nachvollziehen. AbstractAssignable implementiert doch auch IAssignable und niemand wird als Parameter ein AbstractAssignable nehmen wollen. Das wird eine abstrakte Klasse sein, von der kein Mensch wissen kann, wer davon in Zukunft was ableiten wird. Jeder wird seine Methode so schreiben, dass sie ein IAssignable frisst. Du schreibst für ein Lagerverwaltungsprogramm eine Lager-Klasse. Welche Methode von dir soll jetzt beispielsweise java.util.AbstractAssignable als Parameter haben wollen? Wenn du ein Zuweisungs-fähiges Lager willst, wirst du doch sicher nicht ein AbstractAssignable verlangen, so dass dir jemand dafür einen Liter Benzin übergibt?

    Vielleicht ist dein Problem mit Interfaces, dass du sie nicht benutzt und so konkret sein willst. Wenn du an deine Parameter Anforderungen hast, verlange ein Interface! Wenn du eine ArrayList zurückgibst, mach IList zum Rückgabetyp! Beim Austausch von Objekten zwischen zwei Methoden hat dich nur die Schnittstelle zu interessieren, die du brauchst oder die der andere braucht (übertrieben gesagt).

    Dieses 'Argument' zielt nur auf Anfänger ab. Hast du noch nie eine Klasse MyJFrame gesehen die alle listener implementiert? Für profis ist es natürlich egal, weil die diese fehler nicht machen. Aber gerade anfänger werden dazu verleitet alle interfaces zu implementieren, während dass zB in C++ nicht der Fall ist.

    Mei, was kann man als Anfänger nicht alles falsch machen... in Java... in C++... 🙂

    Ich sag aber nicht, dass sie schlecht sind, nur, dass man sie nicht braucht.

    wie ein for und ein do while, stimmts?
    nicht alles was 'nicht essentiell' ist, ist nutzlos.
    Beispiel: wozu Generics? das ewige Object hat gereicht.

    Nein, statische Typsicherheit war mangelhaft. Generics bringen etwas, was ich nicht einfach durch was anderes austauschen kann.

    Wozu überhaupt Klassen, in C sind wir auch so gut ausgekommen.
    Wozu schleifen, ich habe ein goto und ein if.
    und wozu labels, ich kann ja einfach dem goto sagen: springe 3 zeilen vorwärts, etc.

    Diese Fragen sind doch sinnlos, oder? Alles ist irgendwann mal geil, aber es gibt Dinge, die werden zu 90% falsch angewendet, auch wenn die 10% Leute, die es richtig anwenden es cool finden, ist es vielleicht insgesamt doch bedenklich. Hier muss man halt ne böse Wahl treffen. Das eine Extrem ist C++, dass andere Java. Die Wahrheit liegt vielleicht dazwischen, vielleicht gibt es auch keine Wahrheit. Ich mag es so wie bei C#. Ich kann '+' überladen, aber nicht '+='. Wozu auch, wenn der Compiler das machen kann? Vielleicht ist es irgendwann mal geil, den += was anderes machen zu lassen als den +, aber meistens nicht.

    natürlich hat man bei einem if-goto/for/while/do while/for(each) das problem: welche schleife nehme ich und viele anfänger nehmen eine falsche schleife - aber ist es alles in allem für uns profis nicht besser dass wird die wahl haben?

    Teilsteils. Mir gefällt der Mittelweg von C# zur Zeit am besten. C++ erlaubt einfach zu viel Schrott und verlangt vor allem zu viel Schrott. Aber das siehst du natürlich anders und wenn man sich nicht einig wird, muss jeder in "seiner" Sprache coden. Wenn du glaubst, dass es uneingeschränkt gut ist, jeden Müll machen zu dürfen, wie ein Socket mit dem ++-operator eine Verbindung herstellen zu lassen, den &&- und Komma-Operator zu überladen, kann ich dir nicht zustimmen.

    Ein Interface ist manchmal geiler als Mehrfachvererbung, aber das willst du irgendwie als einziges nicht so sehen. Und IMHO übersiehst du auch, das Interfaces nicht nur im Zusammenhang mit Vererbung genutzt werden (siehe oben). Es ist auch manchmal schöner, die Schnittstelle zu spezifizieren, als sie vom Compiler "ausprobieren" zu lassen.

    Keine Frage. Vielfalt ist gut. Nur steht das im krassen gegensatz zu dem rest deines postings.

    Wieso? Ich sage, man hat sich bei Java (fast) immer für eines entschieden. Bei C++ hat man sich (fast) immer für alles entschieden. Die Interfaces hat man dabei aber vergessen.

    ich habe nichts gegen interfaces, nur sehe ich persönlich den vorteil von "implements interface" auf dem papier. denn wie man zB an Closable in Java sieht, es klappt nicht immer so wie der gedanke war.

    Ja, das hat mich da auch genervt. Sowas ist natürlich dumm, aber ich finde es auch nicht so toll, dass ich nen Algorithmus auf etwas anwenden kann, nur weil gerade die Methoden mit der Signatur da sind. Vielleicht machen die was völlig anderes. Bei Sortable weiß ich halt einfach, dass das Ding sortiert werden kann, wenn ich nur weiß, dass es eine Methode smallerThan() gibt, wer sagt mir, dass die zum Sortieren da ist? Man muss beide Systeme ordentlich benutzen, wer schlampt, hat immer verloren. 😃



  • Helium schrieb:

    @Shade of Mine:
    Ich Teile die Meinung, das Interfaces nicht der Weisheit letzter Schluss sein können. Und siehe da: Die ganzen ganz neuen Sprachen (die aus den Research-Projekten) verwenden alle sogenannte Traits. Das sind quasi Interfaces, wobei Methoden allerdings implementiert sein dürfen. Genial einfach, einfach genial.

    Wie wird das mit den mehrdeutigen Aufrufen dort gelöst? Ich erbe jetzt ein A::foo() und implementiere noch das Interface B, was ein konkretes B::foo() hat. Jetzt rufe ich "mein" foo() auf, was wird nun gemacht?



  • Optimizer schrieb:

    Du bist gezwungen, die Schnittstelle, die du nutzen willst, zu spezifizieren. Mit Vererbung hat das hier an keiner Stelle was zu tun. Es ist lediglich eine Option, Teile eines Interfaces als virtuelle Methode zu implementieren, um die Implementierung redefinieren zu können.

    Nur das Problem dass ich damit habe ist, dass es manchmal verdammt vernünftige default Implementierungen gibt.

    Das ist überhaupt kein Problem. Implementierung erben -> ableiten
    Schnittstelle erfüllen -> Interface implementieren

    wieso sollte das in einem Widerspruch zueinander stehen?

    Nur was ist wenn du mehrere Interfaces (mit sinnvollen default Implementierungen) implementieren möchtest? Und/Oder von einer anderen Klasse erben willst/musst?



  • Das lässt sich nicht allgemein beantworten. Man baut Klassenhierarchien in Java anders auf und es ist durchaus kein Problem, soviel Zeugs zu erben, wie man will. Aber es gibt keine allgemeine Formel zum Umwandeln von C++ Klassenhierarchien in Java Klassenhierarchien. Da muss man schon umdenken und ein Gefühl dafür bekommen. Das Java API macht es ja ganz brauchbar vor.



  • Optimizer schrieb:

    Und effizienter!

    Das habe ich indirekt auch geschrieben.

    Das ist überhaupt kein Problem. Implementierung erben -> ableiten
    Schnittstelle erfüllen -> Interface implementieren

    Nur erscheint dass dann in der Schnittstelle, obwohl es ein implementationsdetail ist. Und genau das stört mich.

    Ja, das hat mich da auch genervt. Sowas ist natürlich dumm, aber ich finde es auch nicht so toll, dass ich nen Algorithmus auf etwas anwenden kann, nur weil gerade die Methoden mit der Signatur da sind.

    Ich komme aus einer Welt, da definiert sich ein Objekt durch die Methoden die es hat. Nicht nur bei C++ - ich habe zB schon mit PHP eine 'Klassenhierachie' ohne eine einzige Vererbung aufgebaut.

    Vielleicht machen die was völlig anderes.

    Dito bei Interfaces. Ich kann ein assign() locker so implementieren:

    public void assign(Class other)
    {
      a+=other.a;
      b+=other.b;
    }
    

    und das obwohl ich von IAssignable abgeleitet habe...

    Dieses Argument verstehe ich nicht. Denn wenn ich einen falschen Namen für eine Aktion nehme, dann habe ich einen semantischen Fehler gemacht. Da tut es nichts zur sachen ob ich einfach kreativ war und einen blödsinnigen namen erfunden habe, eine bestehende Methode missbraucht habe oder sonstwas.

    Wie willst du mich daran hindern in add() etwas abzuziehen? nicht wirklich sinnvoll, oder?

    Interfaces bringen diesbezüglich keine sicherheit. Sie bringen hier lediglich den vorteil, dass sie namen standardisieren. Das ist ein schöner effekt, aber dazu braucht man keine interfaces.

    @Helium:
    kannst du mal einen link zu einer dieser sprachen mit schöner erklärung geben?
    Ich habe mixins bisher immer als

    template<class T>
    struct CanA : public T { virtual void A() {} };
    template<class T>
    struct CanB : public T { virtual void B() {} };
    template<class T>
    struct CanC : public T { virtual void C() {} };
    
    class Mixin : public CanA<CanB<CanC> > >
    {};
    

    viel weiter bin ich da noch nicht eingetaucht, aber sprachen die das ganze schöner anbieten (diese templates sind ja eine qual) wären echt interessant.



  • @Shade
    Rubys Mixin-Konzept finde ich z.B. recht hübsch.



  • Shade Of Mine schrieb:

    Dieses Argument verstehe ich nicht. Denn wenn ich einen falschen Namen für eine Aktion nehme, dann habe ich einen semantischen Fehler gemacht. Da tut es nichts zur sachen ob ich einfach kreativ war und einen blödsinnigen namen erfunden habe, eine bestehende Methode missbraucht habe oder sonstwas.

    Wie willst du mich daran hindern in add() etwas abzuziehen? nicht wirklich sinnvoll, oder?

    Interfaces bringen diesbezüglich keine sicherheit.

    Naja, nimm mal ein extremes Beispiel. Du hast eine Toilette und einen BufferedStream. Beides kannst du flushen. Jetzt kann ich also mein geiles C++-Template, was auf ein Objekt flush() aufruft, benutzen. Kann das im Sinne des Erfinders sein? Du hast keinen falschen Namen gewählt, nicht wahr?

    Wenn ich dagegen ein Interface IBuffered habe, dann implementiert mein BufferedStream dieses Interface und ich kann ihn flushen. Ich kann aber die Toilette zum Glück nicht flushen, weil sie nicht IBuffered implementiert. Würde auch keinen sinn machen, weil das flush() einer Toilette nichts mit dem flushen eines Streams zu tun hat.
    Selbst wenn man um 10 Ecken denkt und die Toilette aus irgendeinem Grund ein Interface IBuffered implementieren lässt, wär das immer noch sanitäranlagen.IBuffered und beim Stream io.streams.IBuffered. Man hat hier einfach eine Unterscheidung, die über die Unterscheidung von Methodennamen hinausgeht.

    Du hast natürlich schon Recht, dass eine Klasse und insbesondere die Schnittstelle einer Klasse sich über deren Methoden definiert. Aber die Methodennamen dafür zu verwenden ist halt nicht immer eine gute Idee. 😃 In so einem Fall haben Interfaces schon Vorteile.
    Wenn man es natürlich falsch macht, bzw. vergisst, ein Interface zu implementieren, obwohl man eine solche Methode hat und die auch vom Sinn her diesem Interface entspricht, so wie bei java.net.Socket, dann nervt es, wenn man sie nicht aufrufen kann.



  • Optimizer schrieb:

    Shade Of Mine schrieb:

    Dieses Argument verstehe ich nicht. Denn wenn ich einen falschen Namen für eine Aktion nehme, dann habe ich einen semantischen Fehler gemacht. Da tut es nichts zur sachen ob ich einfach kreativ war und einen blödsinnigen namen erfunden habe, eine bestehende Methode missbraucht habe oder sonstwas.

    Wie willst du mich daran hindern in add() etwas abzuziehen? nicht wirklich sinnvoll, oder?

    Interfaces bringen diesbezüglich keine sicherheit.

    Naja, nimm mal ein extremes Beispiel. Du hast eine Toilette und einen BufferedStream. Beides kannst du flushen. Jetzt kann ich also mein geiles C++-Template, was auf ein Objekt flush() aufruft, benutzen. Kann das im Sinne des Erfinders sein? Du hast keinen falschen Namen gewählt, nicht wahr?

    Das ist kein gutes Beispiel. Zu einem ordentlichen C++ Template gehört mehr als nur ein syntaktischer Vertrag. Jedes Template hat auch einen semantischen Vertrag (auch wenn man den zur Zeit leider hauptsächlich nur über Dokumentation ausdrücken kann - hie könnten Constraints vielleicht wenigstens ein bischen helfen). Ein Template, dass die Nachricht flush an sein Typ-Argument sendet, muss natürlich festlegen, was flush für semantische Bedingungen erfüllen muss. Ein Client muss diese Bedingungen verstehen und einhalten. Das ist ja z.B. beim Überschreiben einer Basisklassenmethode auch nicht anders.

    Aber die Methodennamen dafür zu verwenden ist halt nicht immer eine gute Idee. In so einem Fall haben Interfaces schon Vorteile.

    Imo nicht auf dieser Ebene. Ein Interface bietet ebenfalls keine semantische Garantie (die Semantik legst du in der Doku fest). Du gibst nur die Signatur (statt nur den Namen) der Methoden vor und hast zusätzlich die Möglichkeit das Interface zu bennen.
    Das hindert aber natürlich niemanden daran seine Toilette einfach von IBuffered abzuleiten. Schließlich ist der Spülkasten ja auch eine Art "Puffer".

    Letztlich ist das Ganze die Geschichte mit dem "protecting against murphy vs. protecting against machiavelli". Sprich: Wer Code falsch benutzen will, der schafft das auch. Da schützt dich auch kein Interface.

    Interfaces sind imo letztlich nur die (nötigen) Krücken für statisch geprüfte Sprachen.



  • Shade Of Mine schrieb:

    Es ist einfach nur eine 'struct' dass ein paar Daten beinhalten soll.
    Cool gell?

    warum nicht einfach public variablen? dann isses nicht mehr code als in c++ oder sonst einer anderen sprache. ich mein wenns nur nen struct sein soll...



  • MasterK schrieb:

    warum nicht einfach public variablen? dann isses nicht mehr code als in c++ oder sonst einer anderen sprache. ich mein wenns nur nen struct sein soll...

    ändert nichts an diesen kranken Ctors.

    In der Tat ist es nämlich keine echte 'struct', weil sie nämlich konstant ist. die daten dürfen nachträglich nicht geändert werden.



  • @HumeSikkins: Und dein Code mit den Type Erase und Thunk ist legales C++?



  • undef schrieb:

    @HumeSikkins: Und dein Code mit den Type Erase und Thunk ist legales C++?

    Grundsätzlich ja. Eine ganz kleine Gemeinheit (mal abgesehen von dem VC6-Workaround) ist aber drin 😉



  • Wie wird das mit den mehrdeutigen Aufrufen dort gelöst? Ich erbe jetzt ein A::foo() und implementiere noch das Interface B, was ein konkretes B::foo() hat. Jetzt rufe ich "mein" foo() auf, was wird nun gemacht?

    Hängt wohl von der konkreten Sprache ab, oder?

    In meiner Lieblinssprache wäre es so, dass die Version aus dem Trait die aus der Basisklasse überschreibt.

    kannst du mal einen link zu einer dieser sprachen mit schöner erklärung geben?

    Zum Thema Mixins gibts viele Sprachen (auch ältere). hmm ... gbeta als Beta-Erweiterung, JAM (JAva with Mixins), Scala, ...



  • HumeSikkins schrieb:

    undef schrieb:

    @HumeSikkins: Und dein Code mit den Type Erase und Thunk ist legales C++?

    Grundsätzlich ja. Eine ganz kleine Gemeinheit (mal abgesehen von dem VC6-Workaround) ist aber drin 😉

    Ich dachte Memberfunktionszeiger können unterschiedlich groß sein. (siehe FastDelegate-Artikel auf CodeProject).
    Wenn der Compiler für den generischen Memberfunktionszeiger z.B. 4 bytes reserviert, aber man dann ein Memberfunktionszeiger 8 bytes groß ist müsste es doch schief gehen.
    Was hab ich falsch verstanden? 🙂



  • Achso ich sehe gerade das für unbekannte Sachen immer der größt-mögliche Speicher reserviert wird. 🙂


Anmelden zum Antworten