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



  • Optimizer schrieb:

    Nein, wenn du schon so fragst. Ist aber trotzdem ein Vorteil von Interfaces, dass man damit Mehrfachvererbung vermeiden kann.

    Mag sein, ich sehe interfaces einfach nur als verkrüppelte abstrakte Klassen...

    Schließlich kann ein Interface nichts besonderes. Und man muss sich nur mal diese Codeverdoppelung ansehen die man dadurch bei Java dauernd hat:

    Es gibt ein Interface Foo und dann gibt es die Klasse AbstractFoo, welche vernüftiges Defaultverhalten implementiert.

    Der Sinn erschließt sich mir leider nicht. Vorallem weil man gerade durch Interfaces ja in Java (natürlich nur von Anfängern) Klassen sieht, die 100.000 interfaces implementieren, während von 3 Sachen gleichzeitig erben in C++ schon fast seltenheitswert hat 😉

    Welche Sprache ist hier also einfacher, aus der Sicht eines Anfängers?

    Deshalb gibt es ja Overloading und default-Parameter,

    Klar, folgendes rult in Java einfach:

    final DBRes res;
      final String name;
      final String type;
      final String upType;
      final String title;
      final String value;
      final String externValue;
      final DBRes reloadQuery;
    
      public ComponentDescriptor(DBRes res, String name, String type, String upType, String title, String value, String externValue, DBRes reloadQuery)
      {
        this.res=res;
        this.name=name;
        this.type=type;
        this.upType=upType;
        this.title=title;
        this.value=value;
        this.externValue=value;
        this.reloadQuery=reloadQuery;
      }
    
      public ComponentDescriptor(DBRes res, String name, String type, String upType, String title)
      {
        this(res, name, type, upType, title, null, null, null);
      }
    
      public ComponentDescriptor(DBRes res, String name, String type, String upType, String title, String value)
      {
        this(res, name, type, upType, title, value, null, null);
      }
    
      public ComponentDescriptor(DBRes res, String name, String type, String upType, String title, String value, String externValue)
      {
        this(res, name, type, upType, title, value, externValue, null);
      }
    

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

    virtuell und nicht-virtuell,

    du vergisst einfach das ZeroCost-Principle.
    Java hat dafür ja final 😉 Ist das genauso unnötig?

    Ich kann das ganze aber auch lustigerweise umdrehen:
    wozu brauche ich Interfaces wenn ich KLassen habe. Die antwort wird sein: ausdruckskraft, denn ein Interface ist ja doch etwas anderes als eine Klasse.

    Nun ist der Unterschied virtuell/nicht virtuell aber wohl größer als Klasse/Interface, oder?

    Oder zB float und int - wozu? float reicht doch.
    Wozu überhaupt Klassen? Funktionen tun doch das selbe - man hat halt immer ein switch() ist ja auch nicht so schlimm.
    Wozu überhaupt *? man kann ja einfach ganz oft + rechnen...
    ne,ne,ne, das geht einfach in die falsche richtung...

    usw., obwohl man mit eins von beiden eigentlich immer auskommen könnte. Aber vielleicht ist mal wirklich gerade eins besser als das andere.

    Eben. Genau das ist der Punkt:
    Will man eine Sprache die einem alles erlaubt, auch blödsinn. Oder
    will man eine Sprache die einem keinen blödsinn erlaubt, und somit auch geniestreichs verbietet.

    Es wird sicherlich genauso mal eine Situation geben, wo ein Interface geiler ist als Mehrfachvererbung.

    Nur ist der Punkt, dass in C++ 1:1 Java interfaces mit abstrakten Klassen machen kannst. Vielleicht haben Java Interfaces irgendwo einen kleinen Unterschied, aber für die 08/15 verwendung ist der unterschied nicht vorhanden.

    Falls ich etwas übersehe: nur her damit!



  • Es wird sicherlich genauso mal eine Situation geben, wo ein Interface geiler ist als Mehrfachvererbung

    Definitiv. Interfaces lassen sich nämlich viel effizienter Implementieren als abstrakte Basisklassen. In C++ führt intensive Mehrfachvererbung gerne mal zu "virtual code bloat", unnötig viele vtables mit unnötig vielen RTTI-Objekten. In C++ ist es also in der Regel nicht so sinnvoll das Interface-Segregation-Principle (ISP) explizit über Protokoll-Klassen zu implementieren.
    Leite ich z.B. von vier Klassen ab (die logisch eigentlich nur Interface sein sollen), so erhalte ich vier zusätzliche vtables inklusive RTTI-Objekte, sowie vier unterschiedliche vptr pro Objekt und vier verschiedene Adressen, alles eigentlich unnötig.

    Wer eine effiziente Implementation von Interface für C++ sucht, dem sei ein Blick hierauf empfohlen:
    http://www.kangaroologic.com/interfaces/libs/interfaces/doc/index.html



  • @Hume:

    coole Sache! Hast du die schonmal eingesetzt und ist die Performance wirklich (wesentlich) besser als bei der Realisierung mit virtuellen Funktionen? Oder wirkt sich der Vorteil erst be exessiver Weitervererbung aus? In der Performance Section
    habe ich nur "...sometimes faster..." gelesen.

    Ich hab gerade mal ein bisschen mit einer eigenen C++ Implementierung von Delegates rumgespielt und dort habe ich einen virtuellen Funktionsaufruf, der mich logischerweise eine ganze Menge Performance kostet und den ich einfach nicht los werde. Vielleicht ist das genau das, was ich gesucht habe.



  • TheBigW schrieb:

    @Hume:
    coole Sache! Hast du die schonmal eingesetzt

    Habe eine selbstgeschriebene Version davon mal einegesetzt. Der größte Vorteil ist die geringere Objektgröße wenn du von vielen Interfaces erbst. Ob du Geschwindigkeitsvorteile hast, hängt immer von der konkreten Situation ab (wie oft werden die Funktionen z.B. aufgerufen). In kleinen Benchmark-Anwendungen ist man typischerweise ca. 50% schneller.

    Ich hab gerade mal ein bisschen mit einer eigenen C++ Implementierung von Delegates rumgespielt und dort habe ich einen virtuellen Funktionsaufruf, der mich logischerweise eine ganze Menge Performance kostet und den ich einfach nicht los werde

    Delegates mit virtuellen Aufrufen zu implementieren ist problematisch. Zumindest wenn du für jeden Typ eine Invoker-Klasse erstellst. In diesem Fall hast du wieder "virtual code bloat".

    Für delegates verwendet man in der Regel "type erasure" und thunks.
    1. "Type erasure":
    Du speicherst alle Memberfunktionszeiger über einen generischen Memberfunktionszeiger:

    struct Generic;
    typedef void (Generic::*GENRIC_PMF)();
    
    // a combination of an object and a member funtion pointer
    // both stored in a "type erased" manner
    struct closure
    {
        closure()
            : obj_(0)
            , pmf_(0)
        {}
        void* obj_;
        GENERIC_PMF pmf_;
    };
    

    2. Thunk:
    Kleine Funktion die erst Typeinformationen wiederherstellt und dann Memberfunktion aufruft.

    template <class T, class PT, class R, class P1>
    struct thunk_1
    {
        typedef PT pmf_t;
        static R pmf_thunk(const closure& c, P1 p1)
        {
            return (static_cast<T*>(c.obj_)->*reinterpret_cast<pmf_t>(c.pmf_)(p1);
        }
    };
    

    Ein Delegate-Type sieht dann (stark verkürzt) so aus:

    template <class R, class P1>
    class delegate1
    {
        typedef R (*thunk_type)(const closure&, P1 p1);
    public:
        delegate1()
            : thunk_(0)
        {}
    
        template <class T, class PMF>
        delegate1(T* obj, PMF pmf)
        {
            bind(obj, pmf);
        }
    
        // binding
        template <class T, class Y, class R, class AP1>
        void bind(T* obj, R (Y::*pmf)(AP1))
        {
            typedef R (Y::*PMF)(AP1);
            Y* ro = o;
            this->c_.obj_ = ro;
            this->c_.pmf_ = reinterpret_cast<detail::GENERIC_PMF>(pmf);
            this->thunk_ = &yadi::detail::thunk_1<Y, PMF, R, P1>::pmf_thunk;
        }
    
        // invoking
        R operator()(P1 p1) const
        {
            assert(is_bound());
            return thunk_(c_, p1);
        }
    private:
        closure c_;
        thunk_type thunk_;
    };
    

    Das ist natürlich alles sehr vereinfacht. Wenn du willst kann ich dir eine komplette (VC6-kompatible) Version schicken, die neben (const- und non-const)-Memberfunktionen auch freie Funktionen unterstützt.

    Wennn es dir allerdings um maximale Performance geht, schau dir mal die
    FastDelegates an:
    http://www.codeproject.com/cpp/FastDelegate.asp

    Nachteil: Der Code dort hat undefiniertes Verhalten.

    Andere Alternativen: boost::function (relativ langsam), Loki::Functor



  • Genau durch den Codeproject Artikel bin ich darauf gekommen, mal selbst herum zu probieren, getreu dem Motto: "Das muß doch auch enfacher gehen". Leider wie beschrieben mit mäßigem Erfolg.
    Mir ging es in erster Linie nichtmal darum, das ich die Performance unbedingt brauche (selbst die Variante mit virtuellen Funktionen wäre für ein paar kleine GUI - Callbacks noch schnell genug), sondern eher um den Lerneffekt.

    Wenn ich das richtig verstanden habe castest du also beim "Einpacken" in einen allegemeinen Typ, den du dann bei der Ausführung in den speziellen Typ zurückcastest.

    Das ist natürlich alles sehr vereinfacht. Wenn du willst kann ich dir eine komplette (VC6-kompatible) Version schicken, die neben (const- und non-const)-Memberfunktionen auch freie Funktionen unterstützt.
    

    Gerne!



  • Shade Of Mine schrieb:

    Optimizer schrieb:

    Nein, wenn du schon so fragst. Ist aber trotzdem ein Vorteil von Interfaces, dass man damit Mehrfachvererbung vermeiden kann.

    Mag sein, ich sehe interfaces einfach nur als verkrüppelte abstrakte Klassen...

    Schließlich kann ein Interface nichts besonderes.

    Siehst du while-Schleifen auch als verkrüppelte for-Schleifen?



  • Shade Of Mine schrieb:

    Optimizer schrieb:

    Nein, wenn du schon so fragst. Ist aber trotzdem ein Vorteil von Interfaces, dass man damit Mehrfachvererbung vermeiden kann.

    Mag sein, ich sehe interfaces einfach nur als verkrüppelte abstrakte Klassen...

    Das ist aber eine völlig falsche Vorstellung. Ich kann sie dir nicht verübeln, denn von C++ aus kennst du dieses Konzept zwar, aber die Verbindung zu den Java-Interfaces kannst du vielleicht nicht herstellen (verzeih mir diese Unterstellung), weil es in C++ das nicht als Sprachmittel gibt. Nimm mal std::sort

    std::sort(myvec.begin(), myvec.end())
    

    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.
    Jetzt kommt aber der wichtigste Punkt: Es hat mit Vererbung nicht zwangsläufig was zu tun. Deshalb hat es auch nicht zwangsläufig was mit einer ABC zu tun.

    Sieh dir C# an:

    struct Point : ICompareable
    {
        int x;
        int y;
    }
    

    structs kennen keine Vererbung, die Dinger sind das, was du glaub ich als PODs bezeichnest. Ein Interface ist nicht mehr und nicht weniger als eine Schnittstelle. In C++ nutzt du im Templates einfach den operator< und es geht halt oder nicht. In C# musst du halt sagen, dass es so etwas wie diesen operator< geben soll. 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. Das Implementieren eines Interfaces kann natürlich schon als ist-Beziehung aufgefasst werden, aber muss keine Vererbung sein.

    Schließlich kann ein Interface nichts besonderes. Und man muss sich nur mal diese Codeverdoppelung ansehen die man dadurch bei Java dauernd hat:

    Es gibt ein Interface Foo und dann gibt es die Klasse AbstractFoo, welche vernüftiges Defaultverhalten implementiert.

    Finde ich nicht verwerflich. Du versuchst Interfaces als Basisklassen zu sehen und nennst sie deshalb "verkrüppelt". Das ist aber gar nicht ihr Konzept. Ich muss es nochmal sagen, in C++ gibt es dafür kein Sprachmittel, deshalb musst du ein bisschen umdenken IMHO. 🙂
    Aber was mich grad richtig beschäftigt, wo ist hier Codeduplizierung deswegen?

    Der Sinn erschließt sich mir leider nicht. Vorallem weil man gerade durch Interfaces ja in Java (natürlich nur von Anfängern) Klassen sieht, die 100.000 interfaces implementieren, während von 3 Sachen gleichzeitig erben in C++ schon fast seltenheitswert hat 😉

    Hmmmm, ein Beispiel würde hier helfen. Grundsätzlich gibt es noch Subinterfaces, 93476 Interfaces zu implementieren ist sicher kein guter Stil und wohl auch nicht oft nötig.
    Man muss hier IMHO aber eh vorsichtig sein. Klassenhierarchien sehen in Java einfach anders aus.

    Welche Sprache ist hier also einfacher, aus der Sicht eines Anfängers?

    Java. Du tust ja grad so, als wären die Probleme, die Mehrfachvererbung mit sich bringen kann, für Anfänger leicht zu durschauen.

    Deshalb gibt es ja Overloading und default-Parameter,

    Klar, folgendes rult in Java einfach: [...]

    Du hast das richtige Beispiel gebracht, das rult nämlich. Auf das gegenseitige Aufrufen von Konstruktoren warte ich in C++ vergebens. Vielleicht muss ich hier umdenken, aber ich liebe das.
    Ok, ernsthaft: Default-Parameter sind doch nicht ohne Nachteil. Du solltest sie nicht redefinieren, die Reihenfolge der Parameter ist nicht beliebig und brauchst du sie wirklich? Nein. Ich sag aber nicht, dass sie schlecht sind, nur, dass man sie nicht braucht.

    Java hat dafür ja final 😉 Ist das genauso unnötig?

    Nein, final hat nen Sinn, es verbietet das redefinieren. Nicht-virtuell tut das nicht, macht es nur oft zu einer schlechten Idee. Außerdem sind final-Methoden in Java nicht automatisch nicht-virtuell. Mit Sicherheit nicht-virtuell sind nur private und static Methoden. Ich hab aber nichts gegen das virtual-Schlüsselwort, versteh mich nicht falsch. Es ist ja nicht sinnlos. Ich sage nur, dass es streng genommen nicht nötig ist.

    Oder zB float und int - wozu? float reicht doch.
    Wozu überhaupt Klassen? Funktionen tun doch das selbe - man hat halt immer ein switch() ist ja auch nicht so schlimm.
    Wozu überhaupt *? man kann ja einfach ganz oft + rechnen...
    ne,ne,ne, das geht einfach in die falsche richtung...

    Wieso? Ich habe doch selbst gesagt, dass manchmal was anderes geiler ist. 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.

    Eben. Genau das ist der Punkt:
    Will man eine Sprache die einem alles erlaubt, auch blödsinn. Oder
    will man eine Sprache die einem keinen blödsinn erlaubt, und somit auch geniestreichs verbietet.

    Und in jeder Sprache ist wohl was anderes als potenziell blödsinnig angesehen worden. Das wird sich vermutlich auch nie ändern. Ich finde es nur wichtig, dass man ernsthaft versucht, die Konzepte anderer Sprachen zu verstehen (ja, diese Erkenntnis ist bei mir noch nicht so lange her 😃 ).

    Es wird sicherlich genauso mal eine Situation geben, wo ein Interface geiler ist als Mehrfachvererbung.
    Nur ist der Punkt, dass in C++ 1:1 Java interfaces mit abstrakten Klassen machen kannst. Vielleicht haben Java Interfaces irgendwo einen kleinen Unterschied, aber für die 08/15 verwendung ist der unterschied nicht vorhanden.

    Falls ich etwas übersehe: nur her damit!

    Ja... Ganz oben habe ich schon Beispiele gebracht. Auch kriegst du bei Mehrfachvererbung oft mehrdeutige Aufrufe (die der Compiler anmeckert), die du bei Interfaces nie hast. Was äquivalentes zu Interfaces hast du in C++ nicht.

    EDIT: Dieser Beitrag wurde zum letzten mal am 10. Mai gewartet. 😉



  • @TheBigW
    Hab das Ganze einfach mal auf meine Web-Seite gestellt:
    http://fara.cs.uni-potsdam.de/~kaufmann/yadi.zip

    Der Code ist aber mehr "proof of concept" als alles andere.



  • Helium schrieb:

    Siehst du while-Schleifen auch als verkrüppelte for-Schleifen?

    Das ist der Punkt.
    Mich hat es gestört dass davon die rede war, dass soviele Möglichkeiten existieren ein Problem zu lösen.

    Nun wozu überhaupt Schleifen? ein simples if und goto tut es doch auch?

    Und genau das ist IMHO die falsche richtung und etwas was ich nicht verstehe.
    Nehmen wir als Beispiel mal Java her:
    man will die Sprache möglichst einfach machen, klingt logisch. Lustiges Beispiel ist hierbei aber zB keine Default-Parameter, weil man ja Überladung hat.

    Nun ist das für mich das selbe wie: keine for Schleife, weil man ja while hat (oder umgekehrt).

    Andererseits hat man aber for, while, do while, for(each), etc... genau das finde ich lächerlich. Wenn man so explizit sagt: keine verwirrenden Features dann sollte man sich an Eiffel halten und eben nur ein while anbieten.

    Die Frage ist nur: will man das wirklich?
    ich denke: nein.

    weil manchmal ist eine for schleife besser, manchmal rult ein foreach und manchmal will ich wirklich eine while schleife haben. warum sollte ich also nur ein while wollen?



  • 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.

    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.

    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.

    sowas stört mich.

    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.

    Natürlich hat man dann das 'problem', dass man ein interface implementiert und einfach vergisst die Methode zu überschreiben wenn man das defaultverhalten nicht will - nur ist das für mich kein wesentliches argument.

    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?

    Hmmmm, ein Beispiel würde hier helfen. Grundsätzlich gibt es noch Subinterfaces, 93476 Interfaces zu implementieren ist sicher kein guter Stil und wohl auch nicht oft nötig.

    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.

    Java. Du tust ja grad so, als wären die Probleme, die Mehrfachvererbung mit sich bringen kann, für Anfänger leicht zu durschauen.

    Ganz einfach: ein ANfänger erbt nicht von 2 Klassen gleichzeitig. wird ihm so oft vorgesagt 😉

    natürlich ist java gesamt gesehen einfacher, keine frage.

    Du hast das richtige Beispiel gebracht, das rult nämlich. Auf das gegenseitige Aufrufen von Konstruktoren warte ich in C++ vergebens. Vielleicht muss ich hier umdenken, aber ich liebe das.

    klar, das ist ein feature dass super ist.

    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.
    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.

    ganz einfach: weil es manchmal verdammt praktisch ist.

    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?

    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.

    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.

    interfaces wären IMHO _zusätzlich_ zu einer mehrfachvererbung ideal, oder zumindest interessanter für mich. mich stört nur dieses "wozu brauchen wir A, B und C, wenn man mit D eh alles irgendwie machen kann" dass man mit D sachen kann, die man weder mit A, B oder C machen kann, ist klar - aber es gibt sachen wo D einfach nicht so gut ist wie B.

    Ich finde es nur wichtig, dass man ernsthaft versucht, die Konzepte anderer Sprachen zu verstehen (ja, diese Erkenntnis ist bei mir noch nicht so lange her 😃 ).

    Es gibt genug Sachen die ich an Java verstehe. Lediglich CheckedException und das exzessive Interfacing erschließt sich mir nicht.
    Aber zB die Konzepte der anonymen Klassen, synchronized (wohl eines der tollsten sprachfeatures von java), reflection, etc. finde ich super.

    Und ich habe auch nichts gegen Interfaces in der theorie, lediglich die Java Implementation gefällt mir nicht.

    Aber darum geht es mir hier nicht. Es geht mir eigentlich um vielfalt: lieber ein unnötiges feature dass niemand verwendet, wie zB std::valarray als dass ich einmal so ein monster wie ComponentDescriptor implementieren muss (wobei es natürlich auch einen anderen weg geben wird...)

    nochmal: im prinzip geht es mir nicht um Interfaces sondern darum, dass sie angeblich besser sind als Mehrfachvererbung. Sie sind etwas anderes wie Bashar in seiner Signatur so schön stehen hat "'better' implies different", sie können MI nicht ersetzen. Genausowenig kann MI interfaces ersetzen, dass habe ich durch dein Posting gelernt.



  • Shade Of Mine schrieb:

    Genausowenig kann MI interfaces ersetzen, dass habe ich durch dein Posting gelernt.

    MI kann interfaces nicht ersetzen? das hab ich jetzt nicht so gelesen. hab ich's nur überlesen?

    also ich versuche es mal. ein interface ist eine basisklasse ohne attribute, die aber pur virtuelle methode hat und damit von den erben die existenz dieser pur virtuellen methoden fordert.

    was können jetzt interfaces mehr?



  • volkard schrieb:

    MI kann interfaces nicht ersetzen? das hab ich jetzt nicht so gelesen. hab ich's nur überlesen?

    Ich kann es auch falsch verstanden haben, aber so wie ich es verstanden habe:

    Interface sind keine konkreten Klassen, dh, sie existieren im eigentlichen Code nicht wirklich. Sie sind Compilermagie.

    Sprich: wenn ich von einer abstrakten Klasse erbe, ist die Methode foo() automatisch virtual (sonst könnte ich sie ja nicht überschreiben) bei interfaces ist das aber etwas anders. Da ein Interface selber nicht wirklich existiert, muss ich lediglich foo() implementieren, was ich aber nicht 'überschreibe' sondern lediglich durch Compilermagie gezwungen werde zu implementieren.

    Daraus ergibt sich, dass ich keine 'nutzlosen' vtable einträge für das interface habe.

    Weiters bieten Interfaces die technische Möglichkeit von Runtime-Constraints. dh, ich habe in der vtable nur den eintrag 'implements IAssignable' ohne jetzt anderen ballast zu haben, wie zB direkt Methoden von IAssignable oder gar soetwas wie einen Ctor aufruf - und kann durch reflexion dass "implements IAssignable" aber rauslesen.

    Oder auch alles falsch rum 😞
    Ich bin irgendwie verwirrt.



  • @Shade
    Ist schon mehr oder weniger richtig. Durch eine ABC bekommst du grundsätzlich schonmal eine zusätzliche vtable. Die ist für ein Interface aber unnötig, da dort in jedem Slot sowieso nur "not-implemented-here" drin steht.

    Deshalb bietet z.B. der VC das Attribut novtable. Das sorgt dafür, dass die überflüssigen vtables von ABCs vom Linker entfernt werden. Damit reduzierst du den "per class overhead".

    Dummerweise bekommt ein Objekt einer Klasse die von mehreren ABCs erbt aber natürlich auch mehrere vptr, nämlich einen für jede zusätzliche Basisklasse.
    Beispiel:

    struct  I1 {
    public:
      virtual ~I1() {}
      virtual void f() = 0;
    };
    
    struct  I2 {
    public:
      virtual ~I2() {}
      virtual void g() = 0;
    };
    
    struct  I3 {
    public:
      virtual ~I3() {}
      virtual void h() = 0;
    };
    
    struct Impl : public I1, public I2, public I3
    {
      void f() {}
      void g() {}
      void h() {}
    };
    

    Impl-Objekte sind auf typischen Compilern 12 Byte groß. Obwohl weder Impl noch die Interfaces irgendwelche Daten enthalten. Die 12 Byte stammen einzig aus den drei vptrs. Für MI ist das eine sinnvolle Implementation. Dort besteht ein Objekt aus mehreren Teil-Objekten und hat damit mehrere Objektadressen. Der Compiler muss also sowieso immer ein Offset-Adjust machen. Legt er jetzt die vptr z.B. jeweils an den Anfang der Teil-Objekte braucht der Compiler nur einmal den Offset anpassen und kann dann sehr effizient virtuelle Methoden aufrufen ( (baseAddress + offset)->vptr[funcIndex]).

    Für Interfaces ist dieser Aufwand aber nicht nötig. Ein Interface hat per Definition keine Implementation. Deshalb muss ein Objekt, dass mehrere Interfaces implementiert weder mehrere Adressen noch mehrere vptr besitzen.

    Ein Interface könnte z.B. einfach als eine Tabelle von Funktionszeigern + einen Objektzeiger implementiert werden. In dem Moment wo du ein Objekt einer Interface-Referenz zuweist, werden dann einfach die Adressen der konkreten Funktionen in die Tabelle kopiert und eine Refernz auf das Objekt gespeichert.
    In Standard-C++:

    class I1
    {
    ublic:
    	I1()
    		: obj_(0)
    		, table_(0)
    	{}
    	template <class T>
    	I1(T& obj)
    		: obj_(&obj)
    		, table_(getTable(static_cast<T*>(0)))
    	{}
    
    	void  f()
    	{
    		table_->f0(const_cast<void*>(obj_));
    	}
    
    private:
    	template <class T>
    	struct Functions
    	{
    		static void  f(void* _this)
    		{
    			static_cast<T*>(_this)->f();
    		}
    	};
    	struct Table
    	{
    		void (*f0)(void*);
    	};
    	template <class T>
    	Table* getTable(T* = 0)
    	{
    		static Table table = {
    			&Functions<T>::f,
    		};
    		return &table;
    	}
    	const void*  obj_;
    	const Table* table_;
    };
    
    struct Impl1 {
    void f() {cout << "Impl1" << endl;}
    };
    struct Impl2 {
    void f() {cout << "Impl2" << endl;}
    };
    
    int main()
    {
    Impl1 impl1;
    Impl2 impl2;
    I1 i1 = impl1;
    I1 i2 = impl2;
    i1.f();    // Impl1
    i2.f();    // Impl2
    }
    


  • @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.


Anmelden zum Antworten