"Klassentyp" übergeben?
-
Hallo,
ich suche eine Möglichkeit eine Klasse (nicht das Objekt) zu übergeben / speichern.
Beispiel:
struct MyStruct {
string Ident ;
class(?) : Klasse;
} MS;..
MS[0].Name = '123F';
MS[0].Klasse = CMeineKlasse;
..um dann folgendes machen zu können:
..
a = *CBasisKlasse ;
a= (*CBasisKlasse) new MS[0].Klasse;
..Unter Delphi geht das mit "class of" und class als Typ.
Wie ist das unter C++ / G++ ?Danke für hilfreiche Antworten.
Gruß
NewBee
-
Nachtrag
die Qualifizierung mit [0] ist hier ein versehen.
Nachher soll es ein array of struct werden!
-
Erstell eine virtuelle Funktion.
virtual MyClass *MyClass::Create() { return new MyClass; } // [...] a = X.Create();
-
Hallo,
danke für die Antwort,
aber
a) müsste dazu MyClass nicht bereits instaniziert sein? (oder eine Klassenfunktion)
b) müsste ich das für alle Unterklassen meinr Basisklasse ableiten.Gibt es keine Möglichkeit, den von mir beschriebenen Weg zu gehen?
Gruß
NewBee
-
abstract factory pattern
-
af schrieb:
abstract factory pattern
Hat m.E. nix mit Klassenübergabe zu tun. factory pattern kapseln nur die Erzeugung. In dieser stehe ich dann ja vor dem gleichen Problem.
-
Vielleicht sowas?
template <typename T> Base* Create() { return new T; } std::map<int, Base* (*)()> map; map[0] = &Create<MyClass>; map[1] = &Create<OtherClass>;Bei fortlaufenden Indizes kannst du auch
std::vectornehmen.P.S.: Bitte hör mit diesem grausamen C-Präfix auf.
-
NewBee2 schrieb:
Unter Delphi geht das mit "class of" und class als Typ.
Wie ist das unter C++ / G++ ?C++ unterstützt Metaklassen, Klassenreferenzen und (virtuelle) Klassenmethoden nicht, insbesondere keine virtuellen Konstruktoren. Du mußt dir irgendeinen Workaround mithilfe von Templates ausdenken. Etwa so:
(Nicht erschrecken, es ist etwas wild; laß dich einen Moment darauf ein, dann leuchtet ein, wieso es sinnvoll ist):
template <class I> struct MetaInterface {}; template <class I, class C> struct MetaClass : MetaInterface <I> {}; struct BaseObject { ... }; // irgendeine Basisklase template <> struct MetaInterface<BaseObject> { virtual BaseObject* create (SomeType someParams) = 0; // quasi virtueller Konstruktor virtual bool supports (SomeType someCondition) = 0; // quasi virtuelle Klassenmethode }; typedef MetaInterface<BaseObject> BaseInterface; // quasi "class of" template <class C> struct MetaClass<Base, C> : MetaInterface<Base> { // Standardimplementation des virtuellen Konstruktors // (man beachte die Kovarianz des Rückgabewertes) virtual C* create (SomeType someParams) { return new C (someParams); } // Standardimplementation der virtuellen Klassenfunktion virtual bool supports (SomeType someCondition) { return false; } }; MetaClass<BaseObject, BaseObject> BaseClass; struct DefaultObject : public BaseObject { DefaultObject (SomeType someParams) { ... } }; MetaClass<BaseObject, DefaultObject> DefaultClass; // benutzt obige Standardimplementation struct SpecializedObject : public BaseObject { SpecializedObject (bool somethingElse, SomeType someParams) { ... } }; template <> struct MetaClass<BaseObject, SpecializedObject> : MetaClass<BaseObject, BaseObject> { // Spezialisierung des virtuellen Konstruktors für SpecializedObject virtual SpecializedObject* create (SomeType someParams) { return new SpecializedObject (false, someParams); } // Spezialisierung der Klassenfunktion virtual bool supports (SomeType someCondition) { ... } }; MetaClass<BaseObject, SpecializedObject> SpecializedClass; BaseInterface* myClassReference = &SpecializedClass; BaseObject* myObject = myClassReference->create (someParams);Disclaimer: Code ist ungetestet.
-
Ich verstehe gerade nicht, warum das abstract factory pattern nicht genau das macht, was du willst?
a = *CBasisKlasse ; a= (*CBasisKlasse) new MS[0].Klasse;würde dann so aussehen:
a = MS[0].Factory->Create();wobei die Factories halt alle ein Objekt, das von CBasisKlasse abgeleitet wurde, zurückgeben (oder besser gesagt einen Zeiger darauf).
Was genau soll das hier bedeuten?
a = *CBasisKlasse ;
-
An Audacias Beispiel würde ich jetzt mal kritisieren, dass man vor lauter templates, Metas, Specializeds, Bases gar nicht mehr sieht, dass das dasselbe ist wie ein simples virtual factory pattern, nur mit templates, Metas, Specializeds und Bases.
Aber wahrscheinlich übersehe ich da gerade auch etwas...
-
Warum überhaupt immer Klassen und virtuelle Funktionen mit möglichst viel Boilerplate-Code benutzen, wenn sich virtuelle Konstruktoren übersichtlich und mit einer Zeile Code pro abgeleiteter Klasse implementieren lassen?
-
Ich kann dir gerade nicht folgen, Nexus.
Aber wie wär's mit []() -> Base* { return new Derived; }
Ziemlich kurz!
-
Decimad schrieb:
Ich verstehe gerade nicht, warum das abstract factory pattern nicht genau das macht, was du willst?
Weil Klassenreferenzen in Delphi viel mehr sind als nur irgendwelche Factories.
Decimad schrieb:
An Audacias Beispiel würde ich jetzt mal kritisieren, dass man vor lauter templates, Metas, Specializeds, Bases gar nicht mehr sieht, dass das dasselbe ist wie ein simples virtual factory pattern
Offenbar habe ich dich tatsächlich soweit verwirrt, daß du nicht erkanntest, daß es nicht dasselbe ist

Mein Beispiel ist so umfangreich, weil ich damit versucht habe, einige der Möglichkeiten von Klassenreferenzen in Delphi nachzubilden. Und wie du schon festgestellt hast, ist es auch ein gutes Argument dafür, stattdessen einfach Delphi zu benutzen, wo man sich nicht explizit mit "Metas, Specializeds und Bases" herumschlagen muß. Aber ich will es gerne nochmal im Einzelnen vorstellen.
Ich beginne mal mit dem Klassenreferenzen-Standardbeispiel: den Dateiformaten. Wir haben eine abstrakte Basisklasse
Image, zu der es etwa die UnterklassenBitmap,PNGImageundJPEGImagegeben könnte. Das sieht zunächst so aus:// Delphi: type TImage = class public constructor CreateCopy(Image: TImage); overload; virtual; abstract; constructor CreateFromStream(Stream: TStream); overload; virtual; abstract; procedure SaveToStream(Stream: TStream); virtual; abstract; end; TBitmap = class (TImage) public constructor CreateCopy(Image: TImage); override; constructor CreateFromStream(Stream: TStream); override; procedure SaveToStream(Stream: TStream); override; end;// C++: class Image { public: virtual void saveToStream (std::ostream& stream) = 0; }; class Bitmap { public: Bitmap (Image* image); Bitmap (std::istream& stream); virtual void saveToStream (std::ostream& stream); };Die handelsübliche Benutzung verläuft in Delphi und C++ gleichermaßen (Exceptionsicherheit ignorieren wir jetzt mal):
function LoadBitmapFromFile (const FileName: String): TImage; var Stream: TStream; begin Stream := ...; Result := TBitmap.CreateFromStream (SomeFileStream); end;Image* loadBitmapFromFile (const std::string& fileName) { std::fstream stream (...); return new Bitmap (stream); }So einfach ist es natürlich meistens nicht; wir wollen ja mehrere Dateiformate unterstützen. Unsere Laderoutine soll in der Lage sein, das Dateiformat zu berücksichtigen. In Delphi:
type TImageClass = class of TImage; function LoadImageFromFile (const FileName: String; ImageClass: TImageClass): TImage; var Stream: TStream; begin Stream := ...; Result := ImageClass.CreateFromStream (SomeFileStream); end; { bescheuertes Anwendungsbeispiel } function LoadBitmapFromFile (const FileName: String): TBitmap; begin Result := LoadImageFromFile (FileName, TBitmap) as TBitmap; { Hier wird eine Klasse als Argument übergeben! } end;In C++ könnten wir das natürlich mit einem Factory-Template nachbilden:
typedef Image* (ImageStreamFactory) (std::istream& stream); template <class T> Image* imageStreamFactory (std::istream& stream) { return new T (stream); } Image* loadImageFromFile (const std::string& fileName, ImageStreamFactory factory) { std::fstream stream (...); return factory (stream); } // bescheuertes Anwendungsbeispiel Bitmap* loadBitmapFromFile (const std::string& fileName) { return &dynamic_cast<Bitmap&> (*loadImageFromFile (fileName, imageStreamFactory<Bitmap>)); }Nun läßt sich der Bildtyp aber meist dem Dateinamen entnehmen; es sollte möglich sein, die Auswahl des Graphikformats zu automatisieren. Wir brauchen so etwas wie ein globales Verzeichnis aller verfügbaren Graphikformate, aus denen unsere Funktion selbst auswählen kann. In C++ wäre das mit obigen Factories folgendermaßen lösbar:
class ImageRegistry { public: void registerImageFactoryForFileExt (const std::string& fileExt, ImageStreamFactory factory); ImageStreamFactory getByFileExt (const std::string& fileExt); }; ImageRegistry theRegistry; void registerKnownImageClasses (void) // muß beim Start der Anwendung aufgerufen werden { theRegistry.registerImageFactoryForFileExt (".bmp", imageStreamFactory<Bitmap>); ... } Image* loadImageFromFile (const std::string& fileName) { std::fstream stream (...); return (theRegistry->getByFileExt (extractFileExt (fileName))) (stream); }Wir möchten aber außerdem noch eine generische Speicherroutine haben, die automatisch die richtige Dateiendung hinzufügt. Die Dateiendung ist ja nicht ein Spezifikum unserer
ImageRegistry; sie gehört zur KlasseImagedazu und sollte auchImage-Objekten bekannt sein. Also nehmen wir etwas Redundanz in Kauf und spendierenImagenoch eine Methode, die die Dateiendung zurückgibt:class Image { ... virtual std::string getFileExt (void) = 0; }; ... std::string Bitmap::getFileExt (void) { return ".bmp"; } void saveImageToFile (Image* image, const std::string& fileName) { std::string fullName = fileName + image->getFileExt (); std::fstream stream (...); image->saveToStream (stream); }Eigentlich ist die Dateiendung aber eine Eigenschaft der Klasse
Image, nicht eines Objektes von diesem Typ; wir wollen auchBitmap::getFileExt()schreiben können. Aber dazu müßtegetFileExt()statisch und virtuell zugleich sein.Tatsächlich geht das in Delphi:
type TImage = class ... class function GetFileExt: String; virtual; abstract; // "class function" entspricht statischer Methode end; TClassRegistry = class public function FindFirstClass<T> (Predicate: TPredicate<T>): T; function FindClasses<T> (Predicate: TPredicate<T>): TArray<T>; end; var ClassRegistry: TClassRegistry; function FindImageClassByFileExt (const AFileExt: String): TImageClass; begin Result := ClassRegistry.FindFirstClass<TImageClass> ( function (TheClass: TImageClass): Boolean begin Result := TheClass.GetFileExt = AFileExt; { GetFileExt() ist eine Klassenfunktion und kann ohne Instanz aufgerufen werden. Zugleich ist es virtuell; was zurückgegeben wird, hängt davon ab, ob TheClass auf TBitmap, TJPEGImage oder TPNGImage verweist. (So wie übliche Zeiger Objektzeiger sind, ist TheClass ein Klassenzeiger.) } end); end; function LoadImageFromFile (const FileName: String): TImage; var Stream: TStream; begin Stream := ...; Result := FindImageClassByFileExt (ExtractFileExt (FileName)).CreateFromStream (Stream); end; procedure SaveImageToFile (Image: TImage; const FileName: String); var FullName: String; Stream: TStream; begin FullName := FileName + Image.GetFileExt; { Ebenso kann GetFileExt natürlich für Objekte aufgerufen werden; es verhält sich auch hier wie eine virtuelle Funktion, abhängig vom Typ des Objektes. } Stream := ...; Image.SaveToStream (Stream); end;Wie man sieht, ist hier die
ImageRegistryzu einer allgemeinerenClassRegistrygeworden; es ist nicht nötig, daß der Registrierungsmechanismus von den Dateiendungen weiß oder überhaupt auf vonImageabgeleitete Klassen beschränkt ist. Es gibt außerdem keine Redundanz bezüglich der Dateiendung: sie wird von der virtuellen KlassenmethodeGetFileExt()zurückgegeben und muß nicht beim Registrieren der Klasse zusätzlich angegeben werden. Außerdem muß die Klasse nicht von Hand registriert werden; dieClassRegistryist von selbst in der Lage, alle im Projekt definierten Klassen zu finden (das ist allerdings ein anderes Thema).Es gibt noch viele andere Möglichkeiten, von den Klassenreferenzen Gebrauch zu machen. Vielleicht möchten wir ein Duplikat einer Graphik im selben Format erstellen können:
function CreateDuplicate (Image: TImage): TImage; begin Result := TImageClass (Image.ClassType).CreateCopy (Image); end;Oder wir beugen uns der Einsicht, daß Dateiendungen im realen Leben nicht immer mit dem Inhalt übereinstimmen müssen, und implementieren die Erkennung von Dateiendungen auf Signaturbasis (z.B. beginnen Bitmap-Dateien immer mit den Bytes "BM", bei PNG-Dateien steht "\x89PNG\x0D\x0A\x1A\x0A" etc.):
type TImage = class ... class function SignatureMatches (Stream: TStream): Boolean; virtual; abstract; end; ... class function TBitmap.SignatureMatches(Stream: TStream): Boolean; var Sig: array[0..1] of AnsiChar; begin if Stream.Size < 2 then Exit (False); Stream.ReadBuffer (Sig, 2); Stream.Position := Stream.Position - 2; Result := (Sig[0] = 'B') and (Sig[1] = 'M'); end; function FindImageClassBySignature (Stream: TStream): TImageClass; begin ImageClasses := ClassRegistry.FindFirstClass<TImageClass> ( function (TheClass: TImageClass): Boolean begin Result := TheClass.SignatureMatches (Stream); end); end;Natürlich reichen die Factories vollkommen aus, wenn der OP nur einen einzigen virtuellen Konstruktor nachbilden will. Aber es ist vielleicht klargeworden, warum man schon zu ein paar "Metas" und "Specializeds" greifen muß, wenn man Klassenreferenzen in C++ nachbilden will.
-
lol, ich hab noch nie ernsthaft gehört, dass irgendwer einen Vorteil von Delphi ggü. C++ argumentieren möchte *duck und wegrenn*
-
Man sollte normalerweise nicht versuchen bestimmte Features von Sprache X in Sprache Y nachzubilden.
Sondern lieber versuchen das was man eigentlich erreichen will in Y so zu implementieren wie es mit Y gut umzusetzen ist.
-
hustbaer schrieb:
Man sollte normalerweise nicht versuchen bestimmte Features von Sprache X in Sprache Y nachzubilden.
Sondern lieber versuchen das was man eigentlich erreichen will in Y so zu implementieren wie es mit Y gut umzusetzen ist.Sehe ich auch so. Ich bin seinerzeit nach einigem Experimentieren davon abgerückt, den oben erwähnten Workaround produktiv zu verwenden. Wenn ich Klassenreferenzen brauche, nehme ich Delphi.
Wenn man in C++ nur einen einzigen virtuellen Konstruktor braucht, nimmt man eben Factories. Und wenn man so etwas wie mein Graphikformat-Beispiel machen möchte, wird man wahrscheinlich auch lieber auf die genaue Nachbildung verzichten und stattdessen zwei Klassenhierarchien parallel pflegen:
class Image; class ImageType { public: virtual Image* createCopy (Image* rhs) = 0; virtual Image* createFromStream (std::istream& stream) = 0; virtual std::string getFileExt (void) = 0; virtual bool signatureMatches (std::istream& stream) = 0; }; class Image { public: virtual void saveToStream (std::ostream& stream) = 0; virtual ImageType& getType (void) = 0; }; class Bitmap; class BitmapType { public: virtual Bitmap* createCopy (Image* rhs); virtual Bitmap* createFromStream (std::istream& stream); virtual std::string getFileExt (void) { return ".bmp"; } virtual bool signatureMatches (std::istream& stream); }; BitmapType bitmapType; class Bitmap { public: Bitmap (Image* image); Bitmap (std::istream& stream); virtual void saveToStream (std::ostream& stream); virtual BitmapType& getType (void) { return bitmapType; } };Die Nachteile sind gut tragbar, solange man nicht überbordenden Gebrauch von diesen expliziten "Typklassen" macht.
-
Und danke für den Exkurs über Klassenreferenzen, tut meinem Tellerrand gut!
