Wann verwendet ihr final bei Klassen?
-
Bei allen Klassen, die nicht als Basisklassen verwendet werden sollen? Bei allen abgeleiteten Klassen, von denen nicht weiter abgeleitet werden soll?
-
Ein nicht virtueller Destruktor?
-
Kellerautomat schrieb:
Bei allen Klassen, die nicht als Basisklassen verwendet werden sollen? Bei allen abgeleiteten Klassen, von denen nicht weiter abgeleitet werden soll?
final schränkt die Nutzbarkeit einer Klasse ein (weil immer einen legitimen Grund geben kann, abzuleiten). Aus diesem Grunde würde ich es nur sehr sparsam einsetzen; vorrangig sollte final bei überschriebenen virtuellen Funktionen stehen. Eine final-Spezifikation bei der Klasse ist eigentlich nur sinnvoll, wenn mindestens eine bereits (nicht-final) überschriebene virtuelle Funktion geerbt wird, die in dieser Klasse final sein soll. So etwas tritt bei Liskov-Vererbung nur selten auf.
final ist ein Feature, das die Art, wie Code geschrieben werden kann, in keiner Weise ändert. Es kann punktuell helfen, Fehler zu vermeiden oder die Performance zu verbessern. Beides ist keine ausreichende Rechtfertigung, es überall, wo es auch nur entfernt möglich wäre, einzusetzen.
-
camper schrieb:
Kellerautomat schrieb:
Bei allen Klassen, die nicht als Basisklassen verwendet werden sollen? Bei allen abgeleiteten Klassen, von denen nicht weiter abgeleitet werden soll?
final schränkt die Nutzbarkeit einer Klasse ein (weil immer einen legitimen Grund geben kann, abzuleiten). Aus diesem Grunde würde ich es nur sehr sparsam einsetzen...
Nach meiner überaus schlechten Erfahrung im Projekt mit Entwicklern die unbedacht ableiten, würde ich es eher häufiger einsetzen - dann muss jemand anderes zumindest darüber nachdenken ob die Klasse wirklich für eine Ableitung geeignet und sinnvoll ist.
Mag aber auch daran liegen das ich inzwischen Vererbung kritisch gegenüber stehe, da ich festgestellt habe das die meisten Vererbung einfach zu häufig verwenden, und nicht mal über Alternativen nachdenken. Es ist wesentlich schwerer eine tiefe Vererbungshirachie zu warten und zu ändern, als wenn man Vererbung mit bedacht (und in der Regel mit eher flachen Hirachien) verwendet.
Der sinnvolle Weg liegt aber sicherlich am ehesten zwischen beiden Extremen.
-
asc schrieb:
... - dann muss jemand anderes zumindest darüber nachdenken ob die Klasse wirklich für eine Ableitung geeignet und sinnvoll ist.
Das ist der Grund, wieso ich in C# bei meinen Vorlagen für eine neue Klasse automatisch ein
sealed
drin habe. Wenn ich das Schlüsselwort wegnehme, dann habe ich mir Gedanken dazu gemacht, ob man von der Klasse wirklich erben kann, bzw. ob man überhaupt können soll.Über die Möglichkeit, dass von einer Klasse geerbt werden kann, wird meistens viel zu wenig nachgedacht. Weder der Programmierer, welche von der Klasse erbt, noch derjenige, welcher die Klasse schrieb. Mit einem solchem Schlüsselwort wirkt man dem entgegen. Es hilft auch einem selbst.
Grüssli
-
Ich vertrete ja die Meinung, dass man das ganze umdrehen muesste, sprich:
- Eine Klasse kann per default nicht als Basisklasse verwendet werden (Compile-Error)
- Wird eine Klasse als 'static_base' markiert, so darf sie als Basisklasse verwendet werden, aber hat einen protected dtor.
- Wird eine Klasse als 'polymorphic_base' markiert, so darf sie als Basisklasse verwendet werden, hateinen virtual public dtor und keine Compiler-generierten Kopiersemantiken.Dass das bestehenden Code brechen wurde, ist klar. Man muesste C++ mal neu machen.
-
Kellerautomat schrieb:
Ich vertrete ja die Meinung, dass man das ganze umdrehen muesste, sprich:
- Eine Klasse kann per default nicht als Basisklasse verwendet werden (Compile-Error)
- Wird eine Klasse als 'static_base' markiert, so darf sie als Basisklasse verwendet werden, aber hat einen protected dtor.
- Wird eine Klasse als 'polymorphic_base' markiert, so darf sie als Basisklasse verwendet werden, hateinen virtual public dtor und keine Compiler-generierten Kopiersemantiken.Dass das bestehenden Code brechen wurde, ist klar. Man muesste C++ mal neu machen.
Es gibt auch noch private und protected Vererbung.
Eine Schnittstelle sollte den Nutzer dieser Schnittstelle nicht einschränken, soweit die Funktion der entsprechenden Komponente nicht betroffen ist (das Prinzip hat auch noch einen Namen, der mir gerade entfallen ist).
Wahllose Verwendung von final führt genau zu so Einschränkung, so wie es const-Rückgaben bei Funktionen tun.Wenn in einer (vollständigen) Liskov-Vererbungshierarchie die Blätter mit final gekennzeichnet werden, ist das in Ordnung - dafür ist final da.
Klassen, die sowieso nicht Teil einer solchen Hierarchie sind und kein polymorphes Verhalten haben, mit final zu kennzeichnen, ist dagegen sinnlos. Die Gründe, weshalb von solchen Klassen sowieso (normalerweise) nicht abgeleitet werden sollte, reichen aus. Der Programmierer, der es trotzdem tut, lernt nichts, wenn man es ihm explizit verbietet.
-
camper schrieb:
Klassen, die sowieso nicht Teil einer solchen Hierarchie sind und kein polymorphes Verhalten haben, mit final zu kennzeichnen, ist dagegen sinnlos. Die Gründe, weshalb von solchen Klassen sowieso (normalerweise) nicht abgeleitet werden sollte, reichen aus. Der Programmierer, der es trotzdem tut, lernt nichts, wenn man es ihm explizit verbietet.
Das Argument erschliesst sich mir jetzt nicht so. Klassen von denen man nicht ableitet, sollte man deiner Meinung nach nicht mit
final
gezeichnen, weil man das sowieso nicht tut. Und falls jemand es doch tut, ist dieser selber Schuld? Also statt es zu verhindern, soll man Leute ins Messer laufen lassen, bzw. zulassen, dass schlechte Software produziert wird?Wenn es jetzt irgendein Mehraufwand wäre, ok. Aber da man es sowieso in eine Vorlage reinsetzen kann, wieso nicht gleich tun? Man erbt ja eben nicht davon, wieso sollte man sowas nicht auch gleich kennzeichnen?
Mit deiner ganzen restlichen Argumentation bin ich einverstanden. Nur genau deshalb sollte man
final
in der Vorlage verwenden. Damit sich jemand Gedanken darüber macht, was die Blätter sind und vor allem welche nicht. Was zur Schnittstelle gehört und was nicht.Grüssli
-
Dravere schrieb:
camper schrieb:
Klassen, die sowieso nicht Teil einer solchen Hierarchie sind und kein polymorphes Verhalten haben, mit final zu kennzeichnen, ist dagegen sinnlos. Die Gründe, weshalb von solchen Klassen sowieso (normalerweise) nicht abgeleitet werden sollte, reichen aus. Der Programmierer, der es trotzdem tut, lernt nichts, wenn man es ihm explizit verbietet.
Das Argument erschliesst sich mir jetzt nicht so. Klassen von denen man nicht ableitet, sollte man deiner Meinung nach nicht mit
final
gezeichnen, weil man das sowieso nicht tut. Und falls jemand es doch tut, ist dieser selber Schuld? Also statt es zu verhindern, soll man Leute ins Messer laufen lassen, bzw. zulassen, dass schlechte Software produziert wird?Nein, was ich meine ist, dass es nahezu keine Klassen gibt, von denen man nie ableiten sollte, sondern nur solche, von denen man es fast immer nicht tun sollte.
In C++ heißt ableiten ja nicht automatisch, dass man die Basisklasse als polymorphes Interface benutzen möchte.
Beispielsweise ist es umständlich, so etwas wie boost::compressed_pair zu implementieren, wenn final im Weg sein kann. Zudem gibt es dummerweise kein trait is_final (und es gibt wahrscheinlich keinen Weg, diese mit Sprachmitteln nachzubauen).