Forum: PC-Programmierung Interface vs. Override von BasisklassenMethoden [C++ / C#]


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Jasson J. (jasson)


Lesenswert?

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.

von Roger S. (edge)


Lesenswert?

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.

von Markus L. (rollerblade)


Lesenswert?

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?

von Oliver S. (oliverso)


Lesenswert?

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

von Jasson J. (jasson)


Lesenswert?

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.

von Oliver S. (oliverso)


Lesenswert?

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
von Ob S. (Firma: 1984now) (observer)


Lesenswert?

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.

von Klaus H. (klummel69)


Lesenswert?

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.

von Markus L. (rollerblade)


Lesenswert?

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

von Jasson J. (jasson)


Lesenswert?

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.

von Jasson J. (jasson)


Lesenswert?

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

von Markus L. (rollerblade)


Lesenswert?

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.

von Carsten P. (r2pi)


Lesenswert?

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.

von Rolf M. (rmagnus)


Lesenswert?

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

von Falk S. (falk_s831)


Lesenswert?

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
Noch kein Account? Hier anmelden.