Apfel a = new Obst( "Apfel" ); ?
-
Moin Moin!
Folgendes ist eine eher theoretische Frage, die sich über eine statische Factory-Methode erschlagen läßt. Ich weiß das - anders ausgedrückt: ich suche nicht nach irgendeiner Lösung, sondern frage nach der Machbarkeit von exakt folgendem Szenario:
Ich habe eine Klasse, nennen wir sie Obst. Davon sind Äpfel und Birnen abgeleitet. Nun habe ich eine Beschreibung des Obstes, dass ich erzeugen will, zum Beispiel einen string.
std::string Frucht = "Apfel";Aus der Beschreibung möchte ich nun das passende Obst herstellen:
Obst * NeuesObst = Obst::Create( Frucht );Das ist hübsch, aber ich könnte mich für ein
Obst * NeuesObst = new Obst( Frucht );mehr begeistern.
Ist es in C++ [iiirgendwie] möglich einen Konstruktor zu schreiben, der kein Obst erzeugt (was auch nicht wirklich geht, weil Obst ja doch recht abstrakt ist, in dem Fall sogar pure virtual), sondern die beschriebene Frucht? Es geht mir auch nicht darum, ob das jetzt jedem sinnvoll erscheint oder ob man dafür 100kB Sourcecode schreiben muss und new und wer weiß noch überladen muss... es geht mir nur darum, ob es möglich ist, das in C++ zu beschreiben und ob jemand eine Idee hat, wie man das beschreibt.
Thx
-
Ich bin mir ziemlich sicher, dass es nicht geht. Zum ersten blockt der Compiler schon, weil Obst abstrakt ist (und ich bezweifle, dass man das umgehen kann), zum zweiten muss vor dem Aufruf des Konstruktors, der ja die eigentlichen Typinformationen enthält, bekannt sein, wie groß der Speicherbereich ist. So, wie du es geschrieben hast geht es nicht.
Du könntest vielleicht etwas über den new-Operator erreichen, wenn du deine Syntax zuObst* neuesObst = new ("Apfel") Obst;umformulierst, da dann der Typ und damit die Größe im operator new, also bei der Speicherallokierung bekannt ist, aber selbst dann steht dir die Abstraktheit von Obst im Wege.
-
mit dem placement new könnte es gehen - abgrundtief hässlich natürlich

da Obst wohl abstrakt sein dürfte, benutzen wir eine andere klasse: ObstCreator . dazu ein placement new in dieser klasse. jetzt haben wir das problem, dass zunächst ein ObstCreator objekt erzeugt wird - das wird aber erst fertig, wenn der konstruktor beendet ist, und wir müssten dieses objekt erst zerstören bevor wir das neue erstellen. das geht offenbar nicht, denn am ende des konstruktors ist ja schluss - was wir tun können, ist den konstruktor mit einer exception zu verlassen und um diesen konstruktor einen function-try-block kreieren. im zugehörigen catch existierten dann weder das objekt noch dessen bestandteile (basisklassen und memebr) und wir können das zielobjekt anlegen. unglücklicherweise kann das catch eines function-try-blockes nur mit einer exception beendet werden (das heisst, die betreffende exception müsste dann schon ausserhalb aufgefangen werden). ausserdem müssen wir noch verhindern, dass im zuge der exception uns nicht plötzlich noch der speicher freigegeben wird. dazu brauchen wir einen entsprechenden placement operator delete. bekanntlich gibt es kein placement delete - dieser placement operator delete wird NUR aufgerufen, wenn ein objekt per placement new (mit denselben parametern) erzeugt wird und die konstruktion per exception fehl schlägt. also genau das was wir brauchen. kleines nebenproblem: wir müssen noch irgendwie die parameter des placement new an den konstruktor weitergeben - dazu reservieren wir einfach etwas mehr speicher hinter dem objekt und legen die parameter dort ab). also:
struct Obst { virtual ~Obst() {} static void operator delete(void* p) { ::operator delete(p); } }; struct Apfel : Obst {}; struct Birne : Obst (); struct ObstCreator { static void* operator new(size_t,const char*); static void operator delete(void*,const char*) {} ObstCreator(); }; struct ObstMemory { char obst[static_max<size_t,sizeof(Obst),sizeof(Apfel),sizeof(Birne),sizeof(ObstCreator)>::value]; const char* arg; }; void* ObstCreator::operator new(size_t, const char* arg) { ObstMemory* result = static_cast<ObstMemory>( ::operator new( sizeof( ObstMemory ) ); result->arg = arg; return result; } ObstCreator::ObstCreator() try { { throw this; } } catch ( void* p ) { const char* arg = static_cast<ObstMemory*>(p)->arg; // hier jetzt argument auswerten und normal per placement new passendes Objekt anlegen throw static_cast<Obst*>(p); }aufruf dann per
Obst* p; try { new("Apfel") ObstCreator; } catch ( Obst* e ) { p = e; }am besten alles gleich wieder vergessen

-

-

-
camper schrieb:
da Obst wohl abstrakt sein dürfte, benutzen wir eine andere klasse: ObstCreator
[...]
aufruf dann per
Obst* p; try { new("Apfel") ObstCreator; } catch ( Obst* e ) { p = e; }am besten alles gleich wieder vergessen

Ich bin ehrlich begeistert, mit welcher Kreativität die Frage angegangen wird. Ich finde die Lösung mit den Exceptions auch ehrlich irre (positiv gemeint).
Aber sie hat irgendwie nix mit dem Problem zu tun
^^Xin schrieb:
Folgendes ist eine eher theoretische Frage, die sich über eine statische Factory-Methode erschlagen läßt. Ich weiß das - anders ausgedrückt: ich suche nicht nach irgendeiner Lösung, sondern frage nach der Machbarkeit von exakt folgendem Szenario:
Obst * NeuesObst = new Obst( "Apfel" );Es geht mir um genau diese Zeile. Aber die kreativen Lösungen hier, lassen darauf schließen, dass die Beschreibung zwischen sehr unüblich und leider unmöglich liegt. Ich hoffe noch auf ersteres, so dass ich irgendeinen Trick bei C++ übersehen hätte. Die Idee von .filmor werde ich vielleicht mal antesten, das ist nicht exakt das, was ich haben will, aber schonmal verdammt nah dran.
-
template <typename T> class StandardCreator { public: T* operator () () const { return new T; } }; class ObstCreator { public: typedef boost::function<Obst*> creator_type; typedef std::map<std::string, creator_type> map_type; static void* operator new (size_t, const std::string& s) { map_type::iterator i = _map.find (s); if (i != _map.end ()) return i->second (); else throw std::bad_alloc (); } template <typename T> static void register_<T> (const std::string& s) { register_creator (s, StandardCreator<T> ()); } private: static void register_creator (const std::string& s, creator_type c) { _map.insert (map_type::value_type (s, c)); } static map_type _map; };Warum nicht sowas? Um ehrlich zu sein verstehe ich deinen Code nicht so ganz, camper.
@Xin: Es IST unmöglich, solange du C++ benutzt und Obst abstrakt ist. (Wenn du das ABI kennst, kannst du vielleicht noch was mit komischem C-Code machen).
-
.filmor schrieb:
Warum nicht sowas?
das ist besser (hatte nicht berücksichtigt, dass man op new und delete nat. auch für POD klassen übladen kann. das einzige problem dabei ist eher theoretischer natur. weil dann zwei verschiedene objekte (ObstCreator und ein Obst derivat) an der selben adresse leben, ohne das eins das subobjekt oder member des anderen ist. das ist im prinzip undefiniertes verhalten. es könnte sinnvoll sein, noch ein dummy char als member zu benutzen, denn sonst besteht ObstCreator aus mindestens einem paddingbyte, dass der compiler frei verwenden kann (das löst das problem allerdings nicht wirklich).
Um ehrlich zu sein verstehe ich deinen Code nicht so ganz, camper.
das macht nichts. ist sowieso etwas esoterisch.