Abstrakte Klassen
-
Warum werden abstrakte Klassen (ich meine jetzt nicht Klassen, die rein als Interfaces dienen und gar keine Funktionsdefinitionen bieten, sondern solche, die zwar was machen, aber trotzdem abstrakt sind) immer so gekennzeichnet, dass man eine Funktion deklariert, die man mit =0 initialisiert? Wieso macht man nicht einfach den Konstruktor protected? Das sieht doch viel sauberer aus. Die erste Variante ist doch ziemlich unsinnig, immerhin hat man dann ständig eine Funktion, die in der Basisklasse gar nichts macht, die aber in jeder abgeleiteten Klasse redefiniert werden muss. Ich meine, derartige Funktionen haben ja generell von der Sache her mit abstrakten Klassen erstmal nichts zu tun.
-
Das mit dem =0 dient dazu das man es auch schneller erkennen kann. In großen Projekten will man nicht erst nachgucken, ob der Konstruktor protected ist oder nicht, zumal ich diese Schreibweise noch nie gesehen habe.
-
mit dem =0 wird eine Funktion pure virtual und zwingt abgeleitene Klassen, diese zu implementieren
-
Dieses "=0" hinter der Methode kennzeichnet diese eine Methode als abstrakt (d.h. reine Deklaration). Damit ist sichergestellt, daß jede abgeleitete Klasse diese Funktion bereitstellen muß.
Theoretisch könntest du auch einen geschützten Ctor verwenden, um die Erzeugung von Objekten zu umgehen, aber wozu? Eine abstrakte Basisklasse, die keine abstrakten Methoden hat, ist nicht wirklich sinnvoll(und abstrakte Klassen sind in erster Linie reine Interfaces - aber du kannst gerne ein Gegenbeispiel bringen, wo etwas anderes sinnvoll ist)
-
Wir haben einen Raytracer programmiert, wo die Shape klasse abstrakt war und alle abgleiteten klassen abstrakte Funktionen implementieren (z.B zum sich selber darzustellen, kollision usw.). Dies zur verwendung von Polymorphismus.
Gleiches bei einem Szenengraph.Bin allerdings nicht ganz sicher ob der Mutterklasse nicht auch der Funktion eines Interface gleichkommt in diesem Fall. (wenn das so ist, und die abstrakte klasse nicht nur zum Polymorphismus verwendet wird, dann vergesst das post...)
-
CStoll schrieb:
Dieses "=0" hinter der Methode kennzeichnet diese eine Methode als abstrakt (d.h. reine Deklaration).
Es bedeutet nicht "reine Deklaration". Es bedeudet "pure virtual" und das bedeutet nicht "nur Deklaration" auch wenn das der Einfachheit halber immer wieder behauptet wird. Eine rein virtuelle Funktion kann sehr wohl eine Definition besitzen, selbige wird aber (im Gegensatz zu einer virtuellen Funktion) niemals dynamisch gebunden (man kann sich das = 0 als "hat kein Eintrag in der virtuellen Methodentabelle" vorstellen). Wer eine rein virtuelle Funktion aufrufen will, muss dies explizit tun.
Die Bedeutung von Abstrakt ist abhängig vom Kontext. Abstrakt heißt im OO-Sinne "nicht unabhängig instanziierbar". Es beschreibt eine Eigenschaft einer Klasse. Es beschreibt aber weder eine Methodik (was muss ich tun damit eine Klasse abstrakt ist) noch eine Konsequenz für abgeleitete Klassen (was müssen die tun, damit sich nicht abstrakt sind).
In C++ heißt Abstrakt zwar letztlich auch "nicht unabhängig instanziierbar", es steht aber mehr die Methodik und die Konsequenz im Vordergrund. Abstrakt heißt in C++ "Klasse besitzt mindestens eine rein virtuelle Memberfunktion deren final overrider pure virtual ist". Um nicht mehr abstrakt zu sein, muss eine abgeleitete Klasse alle rein virtuellen Memberfunktionen überschreiben.
Damit ergibt sich auch der Unterschied zwischen "abstrakt wegen protected Ctor" und "C++ abstrakt". Ersteres verbietet lediglich die Instanziierung. Letzteres erzwingt zusätzlich ein bestimmtes Interface.
-
Damit ergibt sich auch der Unterschied zwischen "abstrakt wegen protected Ctor" und "C++ abstrakt". Ersteres verbietet lediglich die Instanziierung. Letzteres erzwingt zusätzlich ein bestimmtes Interface.
zumal ein protected konstruktor die betreffende klasse noch nicht einmal ppolymorph macht.
kleine korrektur - der korrektheit wegen - noch; es gibt einen fall, in dem rein virtuelle funktionen beim virtuellen aufruf gefunden werden: im ctor und dtor stimmen statischer und dynamischer typ überein; rufe ich von dort aus eine virtuelle funktion auf, die in dieser klasse rein virtuell ist, so wird diese rein virtuelle funktion aufgerufen. das kann auch dynamisch passieren: ein aufruf aus dem ctor oder dtor in eine funktion, die nicht inline expandiert wird (sonst könnten wir statisch auflösen), so wird ein virtueller aufruf aus dieser funktion heraus auch eine pure virtual funktion finden.
-
Mit einem protected Konstruktor kann man das Erzeugen von Objekten nicht unterbinden:
class Foo { protected: Foo(){} }; class Bar : public Foo { public: Bar(){} void foobar() { Foo f; //genau das möchte man ja vermeiden, bei ner abstrakten klasse } };
-
camper schrieb:
kleine korrektur - der korrektheit wegen - noch; es gibt einen fall, in dem rein virtuelle funktionen beim virtuellen aufruf gefunden werden: im ctor und dtor stimmen statischer und dynamischer typ überein; rufe ich von dort aus eine virtuelle funktion auf, die in dieser klasse rein virtuell ist, so wird diese rein virtuelle funktion aufgerufen. das kann auch dynamisch passieren: ein aufruf aus dem ctor oder dtor in eine funktion, die nicht inline expandiert wird (sonst könnten wir statisch auflösen), so wird ein virtueller aufruf aus dieser funktion heraus auch eine pure virtual funktion finden.
Mein Verständnis für C++ sagt mir zwei Dinge:
a) Das Ganze hat nichts mit inline oder nicht inline zu tun. Es geht einzig darum, ob der Aufruf qualifiziert (statisch, garantiert nicht virtuell)
oder unqualifiziert (potentiell virtuell) stattfindet.b) Deine Korrektur ist keine Korrektur. Dein Beispiel fällt imo unter 10.4/6 und produziert damit undefiniertes Verhalten.
-
HumeSikkins schrieb:
camper schrieb:
kleine korrektur - der korrektheit wegen - noch; es gibt einen fall, in dem rein virtuelle funktionen beim virtuellen aufruf gefunden werden: im ctor und dtor stimmen statischer und dynamischer typ überein; rufe ich von dort aus eine virtuelle funktion auf, die in dieser klasse rein virtuell ist, so wird diese rein virtuelle funktion aufgerufen. das kann auch dynamisch passieren: ein aufruf aus dem ctor oder dtor in eine funktion, die nicht inline expandiert wird (sonst könnten wir statisch auflösen), so wird ein virtueller aufruf aus dieser funktion heraus auch eine pure virtual funktion finden.
Mein Verständnis für C++ sagt mir zwei Dinge:
a) Das Ganze hat nichts mit inline oder nicht inline zu tun. Es geht einzig darum, ob der Aufruf qualifiziert (statisch, garantiert nicht virtuell)
oder unqualifiziert (potentiell virtuell) stattfindet.das ist schon klar.
b) Deine Korrektur ist keine Korrektur. Dein Beispiel fällt imo unter 10.4/6 und produziert damit undefiniertes Verhalten.
wohl wahr. ist heute nicht mein tag ...
-
An dragon90#:
Mit einem protected Konstruktor kann man das Erzeugen von Objekten nicht unterbinden
Doch, das funktioniert komischerweise. Der Code
class Foo { protected: Foo(){} }; class Bar : public Foo { public: Bar(){} void foobar() { Foo f; //genau das möchte man ja vermeiden, bei ner abstrakten klasse } }; int main() { }
gibt folgende Fehler:
Programm.cpp: In member function
void Bar::foobar()': Programm.cpp:4: error:
Foo::Foo()' is protected
Programm.cpp:14: error: within this contextAn cof:
mit dem =0 wird eine Funktion pure virtual und zwingt abgeleitene Klassen, diese zu implementieren
Ja, aber das ist ja genau mein Problem: Wer sagt, dass ich in einer abstrakten Klasse, die kein reines Interface ist, sondern tatsächlich ein paar definierte Funktionen besitzt, auf jeden Fall mindestens eine pure virtual Funktion brauche? Das sind für mich zwei getrennte Themen und deshalb finde ich es unpassend, sie zu mischen.
An HumeSikkins:
Abstrakt heißt in C++ "Klasse besitzt mindestens eine rein virtuelle Memberfunktion deren final overrider pure virtual ist".
Na toll. So kann man das ganze natürlich auch regeln: Indem man die Definition des Begriffs ändert. Das ändert aber nichts daran, dass eine abstrakte Klasse allgemein letztendlich folgendes bedeutet:
Solche Klassen, aus denen tatsächlich Objekte hervorgehen, werden konkrete Klassen genannt. Als abstrakte Klassen bezeichnet man hingegen solche Klassen, aus denen keine Objekte hervorgehen können.
(Quelle: de.wikipedia.org/wiki/Abstrakte_Klasse) und man in C++-Programmen mit der herkömmlichen Variante gezwungen ist, abstrakten Klassen auf jeden Fall eine pure virtual Funktion hinzuzufügen.
An CStoll:
Eine abstrakte Basisklasse, die keine abstrakten Methoden hat, ist nicht wirklich sinnvoll
(und abstrakte Klassen sind in erster Linie reine Interfaces - aber du kannst gerne ein Gegenbeispiel bringen, wo etwas anderes sinnvoll ist)Nun, stell dir doch mal eine Klasse für Figuren in Spielen vor:
Wir haben eine Basisklasse Spielfigur, von der sich die Klassen Spieler und Gegner ableiten.
Die Basisklasse ist abstrakt. In ihr werden die Position der Figur und eine Bewegungsfunktion void bewegung(RICHTUNG richtung) (RICHTUNG ist ein enum-Typ) abgelegt. Die Bewegungsfunktion ändert die Position der Figur und kümmert sich um die Grafik oder was weiß ich.
Die Klasse Spieler hat eine Funktion void bewegung(char taste), die anhand des Tastendrucks die richtige Richtung ausmacht und die Bewegungsfunktion der Basisklasse aufruft,
Die Klasse Gegner hat eine Funktion void bewegung(). Dort wird einfach nur ein Zufallsgenerator oder sowas gestartet, anhand dessen wiederum die Richtung ermittelt wird, die beim Aufruf der Funktion aus der Basisklasse als Parameter mitgegeben wird.
Wir haben hier also eine abstrakte Basisklasse (eine Figur kann nur Spieler oder Gegner sein, aber nicht nur rein eine Figur), jedoch keine pure virtual Funktionen. Welche würdest du denn jetzt als pre virtual nehmen? Die Bewegungsfunktion? Das wäre ziemlich unklug, da die eigentliche Bewegung immer gleich ist und sich nur die Art, wie die Richtung ermittelt wird, unterscheidet. Würde ich die Bewegungsfunktion pure virtual machen, könnte ich mir das Ableiten gleich sparen. Also hat man doch hier nur die Möglichkeit, eine Pseudofunktion void abstrakteKlasse()=0 zu schreiben, die dann in allen abgeleiteten Klassen mit void abstrakteKlasse(){} definiert wird.
-
Ja, aber das ist ja genau mein Problem: Wer sagt, dass ich in einer abstrakten Klasse, die kein reines Interface ist, sondern tatsächlich ein paar definierte Funktionen besitzt, auf jeden Fall mindestens eine pure virtual Funktion brauche?
Gegenfrage: Was willst du mit einer abstrakten Klasse machen, die keine (pur) virtuellen Funktionen definiert hat? Da du davon sowieso keine Objekte anlegen kannst, braucht sie auch selber nichts zu machen (~meine Meinung~).
Wir haben hier also eine abstrakte Basisklasse (eine Figur kann nur Spieler oder Gegner sein, aber nicht nur rein eine Figur), jedoch keine pure virtual Funktionen. Welche würdest du denn jetzt als pre virtual nehmen? Die Bewegungsfunktion? Das wäre ziemlich unklug, da die eigentliche Bewegung immer gleich ist und sich nur die Art, wie die Richtung ermittelt wird, unterscheidet.
Ich würde die Funktion pur virtuell definieren, die die Bewegungsrichtung ermittelt (beim Spieler die Tastaturabfrage, beim Gegner ein Zufallsgenerator).
class Figur { int x,y; public: enum Richtung{N,E,S,W}; void bewege(void); { Richtung r=get_r(); ... } protected: virtual Richtung get_r() =0; };
(auf diese Weise könntest du auch alle beteiligten Spielfiguren in einem Vector unterbringen und sich am Stück bewegen lassen).
-
Hallo,
in meinen Augen ist diese Diskussion irgendwie wirklichkeitsfremd. Für abstrakte Klassen, die man über eine typische OO-Modelierung erhält, findet man eigentlich immer völlig problemlos eine passende Funktion die pure ist.
Warum? Eine abstrakte Klasse ist zunächst erstmal eine Basisklasse, also sollten für sie grundsätzlich erstmal die gleichen Überlegungen wie für jede andere öffentliche Basisklasse gelten.
In statisch getypten Sprachen wie C++ erbt man öffentlich, nicht primär um den Code der Basisklasse wiederzuverwenden (dafür verwendet man Komposition oder manchmal auch private Vererbung) sondern um anderen Code gegen ein gemeinsames Interface schreiben zu können und diesen Code somit offen gegenüber Veränderungen/Neuerungen zu machen. Das macht allerdings nur sinn, wenn es a) ein gemeinsames Interface gibt und b) die Implementation polymorph ist/sein kann - eine Basisklasse ohne virtuelle Funktionen sollte einem immer zu denken geben.
Somit hat man schonmal eine Menge von virtuellen Funktionen. Eine Basisklasse wird typischerweise abstrakt, weil sie ein noch nicht vollständiges Konzept repräsentiert. Sie beschreibt erstmal nur ein Verhalten (ein Interface) stellt aber keine (vollständige) Implementation bereit, weil ihr dazu konkrete Informationen fehlen. Da es bei Basisklassen primär um Verhalten und nicht um Daten geht, bezieht sich die fehlende Information meist auf einen konkreten Algorithmus und damit auf die Implementation einer virtuellen Funktion. Voilà schon habe ich meine pure-Funktion.
@Wolfgang_01
So wie du dein Figur-Beispiel beschrieben hast, würde ich Figur weder abstrakt machen noch davon erben. Was hast du von einer gemeinsamen Basisklasse, wenn sie kein gemeinsames Interface hat? Erst sagst du Spieler und Gegner *sind* eine Figur, sobald du Spieler und Gegener benutzen willst, musst du aber wieder den genauen Typ rausfriemeln.
Entweder es geht um die Abstraktion von Verhalten (dann würde ich die Richtung von CStoll einschlagen) oder um die Auslagerung von Daten und Algorithmen, dann würde ich Figur als eine "Utility"-Klasse implementieren, die von Spieler und Gegener *benutzt* wird.