Aloá - > mir ist klar, dass es in C++ kein Interfaceschlüsselwort gibt und pure abstract classes gemeint sind. >Frage Gibt es bei C++ / C# einen Unterschied, ob entweder von einer Basisklasse aus abgeleitet wird und einfach jede Methode overriden wird, oder das jeweilige Sprachpendant eines Interfaces benutzt wird? Am Ende läuft beides soweit ich verstanden habe auf das nutzen der VTable hinaus. (?) >>Oder ist der wesentliche Punkt, dass bei Verwenden von Interfaces der Compiler einen durch Fehlermeldungen zwingt, die Implementierungen zu machen? In einer nicht abtrakten Basisklasse müsste man ja mindestens einen leeren Methodenrumpf angeben und würde dann beim Ableiten Gefahr laufen, etwas wichtiges nicht neu zu implementieren.
Jasson J. schrieb: > Gibt es bei C++ / C# einen Unterschied, ob entweder von einer > Basisklasse aus abgeleitet wird und einfach jede Methode overriden wird, > oder das jeweilige Sprachpendant eines Interfaces benutzt wird? C# kennt keine Mehrfachvererbung, eine Klasse kann nur eine Basisklasse haben, jedoch mehrere Interfaces implementieren. > Am Ende läuft beides soweit ich verstanden habe auf das nutzen der > VTable hinaus. (?) Der Aufruf einer Interface oder virtuellen Methode ist in IL identisch.
Jasson J. schrieb: > Gibt es bei C++ / C# einen Unterschied, ob entweder von einer > Basisklasse aus abgeleitet wird und einfach jede Methode overriden wird, > oder das jeweilige Sprachpendant eines Interfaces benutzt wird? Möchtest Du wissen, wann man das eine und wann das andere verwendet, oder geht es Dir um den vom Compiler generierten Code?
Jasson J. schrieb: > Am Ende läuft beides soweit ich verstanden habe auf das nutzen der > VTable hinaus. (?) Jeder Aufruf einer virtuelle Methode läuft in C++ über eine vtable, egal, ob die pure deklariert wurde oder nicht, sofern der Compiler den Aufruf nicht direkt auflösen kann. Daher ist die Frage: Was ist eigentlich die Frage? Oliver
Markus L. schrieb: > Jasson J. schrieb: >> ... > Möchtest Du wissen, wann man das eine und wann das andere verwendet, > ... Ja genau - darum gehts mir Man könnte ja.. (mit der Formulierung fangen alle dummen Ideen an), eine Basisklasse mit leeren Methodenrümpfen schreiben, die dann fleißig in abgeleiteten Klassen overriden werden. - Dann kann man Instanzen der abgeleiteten Klassen im Basisklassentyp referenzieren. ABER man wird nicht gezwungen, die leeren Rümpfe neu zu schreiben und läuft Gefahr etwas zu vergessen. >Das wäre im Moment meine Erklärung wann Interfaces gegenüber Basisklassen mit leeren Methodenrümpfen sinnvoll sind.
Jasson J. schrieb: > Man könnte Könnte man. Man könnte auch was ganz anderes machen. Letztendlich ist das alles kein Selbstzweck, sondern soll irgend eine Anforderung erfüllen. Mach einfach, was dir an besten gefällt. Jasson J. schrieb: > ABER man wird nicht gezwungen, die leeren Rümpfe neu zu schreiben und > läuft Gefahr etwas zu vergessen. Vergessen kannst du nichts, spätestens der Linker wird dich daran erinnern. Das Konzept "Interface" würde ich in C++ immer mit pure virtual functions implementieren. Oliver
:
Bearbeitet durch User
Jasson J. schrieb: > Gibt es bei C++ / C# einen Unterschied, ob entweder von einer > Basisklasse aus abgeleitet wird und einfach jede Methode overriden wird, > oder das jeweilige Sprachpendant eines Interfaces benutzt wird? > Am Ende läuft beides soweit ich verstanden habe auf das nutzen der > VTable hinaus. (?) Nein, tut es nicht, jedenfalls nicht in C#. Wäre es so, könntest du Implementierungen eine Interface in einer Klasse weiter an davon abgeleitete Klassen verereben. Das kannst du aber nicht. Du musst statt dessen bei jeder abgeleiteten Klasse erneut das Interface implementieren (und dann im einfachsten Fall einfach nur die Implementierung der Vorfahr-Klasse für die jeweilige Methode aufrufen). Und es gibt auch keinen Ausweg daraus. Es bringt nichts, in der ersten Klasse, die das Interface implementiert, überschreibbare Platzhalter-Methoden zu implementieren. Damit kannst du den Kram dann zwar vererben, aber keiner weiß, dass eine davon abgleitete Klasse das Interface (auf diesem Umweg über die VTable) ebenfalls implementiert. Im Großen und Ganzen ist das Verhalten von Methoden implementierter Interfaces am Ehesten mit dem von Konstruktoren zu vergleichen. Mit einer einzigen Ausnahme: Der parameterlose Konstruktor wird (sofern vorhanden) automatisch vererbt.
Oliver S. schrieb: > Jasson J. schrieb: >> ABER man wird nicht gezwungen, die leeren Rümpfe neu zu schreiben und >> läuft Gefahr etwas zu vergessen. > Vergessen kannst du nichts, spätestens der Linker wird dich daran > erinnern. Ich vermute mit leeren Rümpfen meint Jasson sowas in der Basisklasse:
1 | virtual void dosomething() { }; |
statt:
1 | virtual void dosomething() = 0; |
Würde ich nicht unbedingt machen. Denn dann kommt wirklich keine Fehlermeldung vom Compiler/Linker bei Nichtimplementierung in abgeleiteten Klassen. Es sei denn, dass ist genau deine Absicht.
> Ja genau - darum gehts mir Es kommt darauf an, was man damit erreichen möchte. Basisklassen nutzt man, wenn eine Grundfunktionalität bereit gestellt werden soll. Davon abgeleitet implementieren spezialisierte Klassen jeweils die für sie notwendige Funktionaltät. Manchmal weiß man vorher, wann eine Basiklasse angebracht ist, manchmal aber auch erst, wenn man eine zweite, dritte ... Version einer Klasse benötigt. Dann verlagert man die gemeinsame Funktionalität in eine Basisklasse. Oder eine Library stellt Grundfunktionalitäten in Form von Basisklassen zur Verfügung, z.B. ein GUI-System ein Window, das man mit einer eigenen Subclass spezialisieren kann, indem man im Konstruktor Labels, Pushbuttons, Checkbutton, Listeners etc. hinzufügt. Bei Interfaces gibt es unterschiedliche Anwendungsszenarien: a) Wenn Interfaces einen Vertrag definieren: "So mußt Du das machen, sonst weiß ich nichts mit Dir anzufangen". Das sagt der, der den Vertrag vorgibt. Dabei geht's um Polymorphismus, z.B. ein Listener-, Consumer-, Input/OutputStream-Interface. Der, der das Interface vorgibt, erhält eine Instanz der Interface-implementierenden Klasse und ruft dessen (Interface-)Methoden auf. Du schreibst z.B. einen Keyboard-Listener, registrierst ihn bei Deinem GUI-System und bekommst jeden Tastendruck mit. Du instanziierst also Deine Klasse und gibst die Instanz einem Framework, das sie aufruft. Das kann auch Dein eigenes Framework sein. Das Pattern "Don't call us, we call you" paßt in dieses Anwendungsszenario. Anstatt selbst zyklisch das Keyboard abzufragen, wirst Du vom Framework darüber informiert, ob eine Taste gedrückt wurde. b) Bei Aufrufschnittstellen baut man gerne Interfaces, vor allem beim Remoting (Client/Server), z.B. bei CORBA oder EJB-Remoting (Java). Niemand möchte auf dem Client die Klassen kennen, die die Serverfunktionalität implementieren, sondern lediglich das Interface zu dieser Serverfunktionalität. Dazu trennt man auch Interface und Implementierierung in zwei Libraries auf. Der Client kennt nur die mit den Interfaces, der Server beide. c) Inversion of Dependency: Man möchte in komplexem Fachcode (Bank, Versicherung, ...) mit all seinen unzähligen Fallunterscheidungen nicht auch noch komplexen Code z.B. zum Persistieren drin haben, also Code zum Lesen, Speichern, Ändern und Löschen von Daten. Dann baut man sich für jedes Fachobjekt ein Interface, das Methoden zum Zugriff auf seine Attribute, andere beziehungsnahe Objekte enthält etc. So kann z.B. das Interface Kunde die Methode eroeffneKonto( Konto) enthalten. Der Aufrufer dieser Methode (der komplexe Fachcode) weiß aber gar nicht, wohin mit dem neuen Konto, will es auch gar nicht wissen. Das einzige, das ihn interessiert ist, daß ein Konto eröffnet werden soll. Das Konto-Objekt kann er noch selber erstellen, weiß aber nicht wohin damit, also macht er's sich einfach und definiert die dafür zuständige Methode im Interface Kunde. Jemand anders soll sich darum kümmern. Das ist die Klasse, die das Interface Kunde implementiert. Diese Klasse muß jemand instanziieren, der diesen komplexem Fachcode aufruft. Das macht die über dem Fachcode liegende Schicht usw. usf... Lange Rede kurzer Sinn: mit Interfaces läßt sich die Abhängigkeit umdrehen, nennt sich Inversion of Dependency. Das macht Sinn bei hochkomplexen fachlichen Anforderungen, die man nicht noch zusätzlich mit Technikkram wie z.B. Datenbankoperation oder XML-Generierung etc. belasten möchte. So einen Mischmaschcode blickt irgendwann keiner mehr. Und: der komplexe Fachcode läßt sich easy peasy unittesten, weil er nur den reinen Fachcode und eben keine Datenbankoperation etc. enthät. Es gibt auch Mischformen, also Basisklassen, die ein (oder mehrere) Interface implementieren. Das macht man dann, wenn Polymorphismus gefordert ist, aber man davon ausgeht, daß alle Klassen, die dieses Interface implementieren, dieselbe Grundfunktionaltät benötigen. > Man könnte ja.. (mit der Formulierung fangen alle dummen Ideen an), eine > Basisklasse mit leeren Methodenrümpfen schreiben, die dann fleißig in > abgeleiteten Klassen overriden werden. Fleiß und Zwang, Methoden zu implementieren, ist nicht die Intention von Vererbung oder Interfaces. Es stecken konkrete Konzepte dahinter, siehe oben a-c. > Dann kann man Instanzen der abgeleiteten Klassen im Basisklassentyp > referenzieren. "this" gibt es in der Sub- und Superclass immer. Die Frage ist, ob "this" überhaupt gebraucht wird.
Klaus H. schrieb: > ... > Ich vermute mit leeren Rümpfen meint Jasson sowas in der Basisklasse: >
1 | virtual void dosomething() { }; |
> > statt: >
1 | virtual void dosomething() = 0; |
> > Würde ich nicht unbedingt machen. Denn dann kommt wirklich keine > Fehlermeldung vom Compiler/Linker bei Nichtimplementierung in > abgeleiteten Klassen. > Es sei denn, dass ist genau deine Absicht. Genau das meinte ich - ja, hätte ich doch wenigstens eine Beispielmethode tippen sollen.
Markus L. schrieb: >> Ja genau - darum gehts mir > Es kommt darauf an, was man damit erreichen möchte. > ... > Bei Interfaces gibt es unterschiedliche Anwendungsszenarien: > a) Wenn Interfaces einen Vertrag definieren: "So mußt Du das machen, > sonst weiß ich nichts mit Dir anzufangen". Das sagt der, der den Vertrag > vorgibt. Die Frage, die ein Interface beantwortet ist nicht das "so musst du das machen", sondern das "was sollst du machen".
Jasson J. schrieb: > Die Frage, die ein Interface beantwortet ist nicht das "so musst du das > machen", sondern das "was sollst du machen". Was meinst Du? Z.B. beim Observer-Pattern (https://refactoring.guru/design-patterns/observer) müssen Deine Subscriber-Klassen das vom Publisher vorgegebene Interface implementieren, sonst kannst Du diese weder bei ihm subscriben noch Events von ihm empfangen. Der Publisher gibt das Subscriber-Interface vor, an das sich alle Subscriber-Klassen zu halten haben.
Jasson J. schrieb: > Gibt es bei C++ / C# Schnon mit diesem Teil der Frage entpuppt sie sich als Müll. Wer C++ und C# nicht unterschieden kriegt, trägt auch Schlüpper als Mützen. Dass im .NET-Habitat daraus ähnliche Executables werden, liegt nicht an der Sprache, sondern an den Compilern und dem Ziel, dass eben beider Code in derselben Runtime funktionieren kann. C# hat eine ganz andere Philosophie als C++/C. Genug gesagt.
Carsten P. schrieb: > C# hat eine ganz andere Philosophie als C++/C. Wer C++ und C nicht unterschieden kriegt, trägt auch Schlüpper als Mützen. 😉
Generell vorneweg ist glaube ich 'Favor composition over inheritance' ein gutes Prinzip fürs SW-Design. Interface-Klassen/Structs in C++ sollten pure-virtual implementiert sein, bis eben auf den Destruktor, der darf in Interfaces nie final sein. Heißt: Destruktor muss im Interface immer virtual sein und auch eine Implementierung haben (z.B. default-Keyword dahinter). Grund: andernfalls potentiell Memleaks, wenn die Basisklasse deleted wird. Zum Thema Inversion-of-Control: im Prinzip bietet dir ANSI-C ja bereits über die Callback-Funktionstypen nen Mittel für die Dependency Inversion. Interface-Klassen stellen einem quasi ne Gruppe von Callback-Funktionen bereit, die aber eben auch an ein Objekt gebunden sind. Letzteres haste bei Callback-Funktionen in C++ alleine nicht, da musst eben der Callback auch noch das Objekt als Parameter mitgeben. Interfaces sind in C++ gut, wenn sie wirklich für eine DI benötigt werden. Man kann's aber auch übertreiben, wenn man z.B. mal in Richtung COM-Objekte lunzt, die früher vermutlich mal der heiße Sch*ss waren, aber eben auch mit ner üblen Kopplung an die Windows-Plattform verbunden sind. CORBA ist da vermutlich nicht viel besser. Ansonsten: wenn Polymorphie gebastelt wird, immer dran denken: nen Hund mit 4 Bonus-Beinen wird kein Oktopus. Deswegen besser Vererbung auf sinnvolles Maß minimieren, weil abgelittenen Klassen sonst schnell sowas entsteht (und was sich dann Jahre später meist eher mühsam rausrefaktorieren lässt).
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.