type erasure und std::exception_ptr
-
Ist euch schonmal aufgefallen dass man
std::exception_ptr"schön" als Mittel zur Type-Erasure einsetzen kann -- völlig unabhängig von Exceptions?boost::anyund ähnliche Konstrukte haben mMn. eine deutliche Schwäche, und zwar kann man keine Basisklassen-Zeiger/-Referenzen von einemanybekommen. Also wenn man z.B. eine polymorphe Basisklasseprintablehat, von der dann mehrere Klassen erben, dann kommt man mitanynicht sehr weit.
Man kann Objekte der abgeleiteten Klassen zwar schön in dasanyhineintun, aber man kann nicht mehr alsprintabledarauf zugreifen. Man bräuchte den exakten Typ. So lange man immer nur Zugriff auf eine Basisklasse braucht könnte man sich natürlich behelfen indem man einenshared_ptr<Basisklasse>in dasanyhineintut. Möchte man aber auf 2 oder mehr Basisklassen zugreifen können, oder u.U. sogar ohne es vorher zu wissen checken ob das imanygespeicherte Objekt eine bestimmte Basisklasse hat, dann ... naja, dann wüsste ich zumindest nicht mehr wie man das machen sollte.Mit C++11 haben wir nun
std::exception_ptrbekommen, und damit geht genau das auf einmal:Nun ist das ja eine ziemlich greisliche Zweckentfremdung von
std::exception_ptr. Die Möglichkeit relativ unkompliziert über Basisklassen auf Objekte in Type-Erasure-Containern alaanyzuzugreifen könnte mMn. aber öfters mal nützlich sein.Und daher frage ich mich: wieso ist kein weniger "brutaler" Weg vorgesehen auf Objekte in einem
std::exception_ptrzuzugreifen?Mir ist schon klar dass keiner von Euch im Standard-Komitte sitzt (bzw. ich gehe davon aus), daher erwarte ich mir auch keine Antwort auf diese Frage. Aber vielleicht findet ja jmd. von Euch das Thema interessant, und hat ein paar Worte dazu zu sagen.
Bzw. auch: würdet ihr diese Funktionalität begrüssen? Für sinnvoll halten? Hattet ihr vielleicht sogar schon Fälle wo ihr euch diese Funktionalität gewünscht hättet?
-
Deine
inspect-Funktion ist falsch. (Keinreturn-Statement)Vorschlag:
#include <iostream> #include <memory> class any_ptr { struct WrapperBase { virtual ~WrapperBase(){} }; template<typename T> struct Wrapper : WrapperBase, T { template<typename ... Args> Wrapper( Args&&... args ): T( std::forward<Args>(args)... ) {} }; std::unique_ptr<WrapperBase> mPtr; any_ptr( std::unique_ptr<WrapperBase> ptr ): mPtr{ std::move(ptr) } {} public: template< typename T > explicit operator T*() { return dynamic_cast<T*>(mPtr.get()); } template< typename T > explicit operator T const*() const { return dynamic_cast<T*>(mPtr.get()); } template<typename T> friend any_ptr make_any_ptr( T&& t ) { return std::unique_ptr<WrapperBase>{ new Wrapper<T>{ std::forward<T>(t) } }; } template< typename T, typename ... Args > friend any_ptr make_any_ptr( Args&&... args ) { return std::unique_ptr<WrapperBase>{ new Wrapper<T>{ std::forward<Args>(args)... } }; } }; //////////////////////////////////////////////////////////////////////////////// template <class T> void inspect(any_ptr& p, void (&fun)(T)) { using actual_t = typename std::remove_reference<T>::type; if( actual_t* t = static_cast<actual_t*>(p) ) fun(*t); } //////////////////////////////////////////////////////////////////////////////// class printable { public: virtual void print() const = 0; }; //////////////////////////////////////////////////////////////////////////////// #include <string> using namespace std; class foo : public printable { public: foo(string const& s) : m_s(s) {} virtual void print() const { cout << "foo: " << m_s << "\n"; } private: string m_s; }; class bar : public printable { public: bar(int i) : m_i(i) {} virtual void print() const { cout << "bar: " << m_i << "\n"; } private: int m_i; }; //////////////////////////////////////////////////////////////////////////////// void print_printable(printable& p) { p.print(); } //////////////////////////////////////////////////////////////////////////////// int main() { auto f = make_any_ptr(foo("foo")); auto b = make_any_ptr(bar(42)); inspect(f, print_printable); inspect(b, print_printable); }Funktioniert nicht für skalare Typen, oder finale Klassen. Sonst aber für eine Vererbungshierarchie doch anwendbar?
Ideone: http://ideone.com/yMrCqb
-
Sone schrieb:
Deine
inspect-Funktion ist falsch. (Keinreturn-Statement)Naja "kein" ist übertrieben, das
return falsewar schon da, dasreturn truehat halt gefehlt.
Hab's korrigiert.Sone schrieb:
Funktioniert nicht für skalare Typen, oder finale Klassen. Sonst aber für eine Vererbungshierarchie doch anwendbar?
Funktioniert auch nicht für Zeiger.
Aber gute Idee, an die Möglichkeit hab' ich nicht gedacht!
-
Und meine Lösung kommentierst du nicht?

-
Ich hab doch schon geschrieben "gute Idee, an die Möglichkeit hab' ich nicht gedacht"

Mit etwas mehr Worten: ja, damit kann man wohl fast alles machen wo man sonst den
exception_ptrHack verwenden wollen würde, und das sogar ganz ohne C++11 (abgesehen von perfect forwarding für diemakeFunktion und ähnliche Kleinigkeiten).
Die Sache mit den non-class Types kann man ja noch relativ einfach lösen (Spezialisierung), bleiben eigentlich nur noch finale Klassen. Und die Sache dass man keine rohen Zeiger so damit verpacken kann, dass man sie als Zeiger auf ne Basisklasse wieder rausholen kann.
(Also nur den Zeiger, ohne das Objekt.)
Was wohl beides super selten nötig sein wird.Also wie gesagt: gute Lösung.
Ich könnte jetzt noch suchen ob ich irgend einen Formfehler/Flüchtigkeitsfehler/Unschönheit etc. finde, aber darum geht's ja nicht. Das Konzept sieht für mich OK aus.
Das einzige was mir auf die Schnelle auffällt ist dass du einendynamic_casthinter einemstatic_castversteckst, was ich nicht so gut finde. Aber wie gesagt, das ist Formsache, und darum geht's ja nicht.BTW: man könnte das ganze sogar noch weiter reduzieren, indem man gleich
shared_ptr<anything>verwendet - wobeianythingdeinemany_ptr::WrapperBaseentspricht. Dann könnte mandynamic_pointer_castverwenden um an einen permanenten Zeiger mit Ownership auf eine Basisklasse zu kommen.
-
hustbaer schrieb:
Ich hab doch schon geschrieben "gute Idee, an die Möglichkeit hab' ich nicht gedacht"

Das hast du editiert.

Guck mal auf die Zeitstempel. Nein, ich wollte tatsächlich nur wissen, ob da nicht noch Fallen versteckt sind die du erkennen könntest.Tja, man kann Wrapper noch partiell Spezialisieren, sodass es dann auch für Skalare klappt, wie du schon erwähnt hast. Finale Klassen wären für diese Idee tatsächlich so nicht realisierbar.
Und die Sache dass man keine rohen Zeiger so damit verpacken kann, dass man sie als Zeiger auf ne Basisklasse wieder rausholen kann.
Hmm, eigentlich ein Einwand... das müsste doch machbar sein...
Das einzige was mir auf die Schnelle auffällt ist dass du einen dynamic_cast hinter einem static_cast versteckst, was ich nicht so gut finde. Aber wie gesagt, das ist Formsache, und darum geht's ja nicht.
Der User muss sich im Klaren sein, dass für den Konvertierungsoperator ein paar Takte draufgehen.
-
Sone schrieb:
hustbaer schrieb:
Ich hab doch schon geschrieben "gute Idee, an die Möglichkeit hab' ich nicht gedacht"

Das hast du editiert.

Guck mal auf die Zeitstempel. Nein, ich wollte tatsächlich nur wissen, ob da nicht noch Fallen versteckt sind die du erkennen könntest.Ja, hab editiert. Die Zeit dazwischen war aber so kurz dass ich nicht damit gerechnet habe dass es jmd. dazwischen gelesen hat.
EDIT: Wenn das Forum nicht spinnt, hab ich das doch nicht editiert. Denn der letzte EDIT betraf nur die
returnSache, den Absatz hab ich später nochmal editiert. Und es steht da ja "1x editiert". Oder zählt das Forum EDITs nicht wenn man sie super kurz nach dem posten macht? /EDITSone schrieb:
Und die Sache dass man keine rohen Zeiger so damit verpacken kann, dass man sie als Zeiger auf ne Basisklasse wieder rausholen kann.
Hmm, eigentlich ein Einwand... das müsste doch machbar sein...
Ich wüsste nicht wie. Falls es doch geht, immer her damit.
Wenn es ginge, dann wäre damit nämlich auch dir Wrapper-Klasse unnötig.Sone schrieb:
Das einzige was mir auf die Schnelle auffällt ist dass du einen dynamic_cast hinter einem static_cast versteckst, was ich nicht so gut finde. Aber wie gesagt, das ist Formsache, und darum geht's ja nicht.
Der User muss sich im Klaren sein, dass für den Konvertierungsoperator ein paar Takte draufgehen.
Ja, pfuh. Ich würde es halt eher über ne Member-Funktion machen. Oder über ne freie Funktion ala
dynamic_pointer_cast.
-
Wenn man beim Wrapper Komposition anstatt Vererbung verwendet, geht das auch für finale Klassen.
Und anstatt einem Crosscast von WrapperBase zu T, macht man halt einen downcast zu Wrapper und greift dort auf T zu.
-
@Nathan
Das geht nicht, weil mit KompositionWrapper<foo>nicht mitWrapper<printable>verwandt ist (und auch nicht mitfoooderprintable).Mit Vererbung ist
Wrapper<foo>aber mitfooverwandt, und dadurch auch mitprintable. Und dadurch kann man casten. Und daWrapperBasene virtuelle Funktion hat, kann man auchdynamic_castverwenden. Das ist ja der Trick der die ganze Sache überhaupt möglich macht.
-
Stimmt, der springende Punkt war ja, Vererbungsstrukturen beizubehalten.
Mein Fehler.
-
Hier die Version mit Skalaren, ich hoffe ich habe nicht zu viel Boilerplate produziert: http://ideone.com/4E9YZS
Mache mich jetzt an die Zeiger-Geschichte.
-
Die Erweiterung auf Skalare ist trivial, das hätte ich dir schon geglaubt dass du das hinbekommst.

Interessant - aber vermutlich unmöglich - ist die Geschichte mit den Zeigern.
-
hustbaer schrieb:
Die Erweiterung auf Skalare ist trivial, das hätte ich dir schon geglaubt dass du das hinbekommst.

Ich dachte, vielleicht will das ja tatsächlich mal jemand benutzen

vermutlich unmöglich
Es ist nicht unmöglich. Denn die Idee ist logisch. Solange die Idee richtig ist, kann und muss es auch richtigen Code geben. Irgendwo da draußen ist ein sauberer, völlig standardkonformer und hübscher Code, der nur darauf wartet von mir in der IDE eingetippt zu werden.
Das hier ist eine unportable und hässliche Variante. Sie ist Implementationsspezifisch und funktioniert wahrscheinlich nicht, laut Standard ist da ebenfalls nichts garantiert (wahrscheinlich UB, könnte man aber sicher auch ohne UB hinbekommen): http://ideone.com/SShuV3
Schon auf Ideone funktioniert die Variante mit den Skalaren nicht mehr. Obwohl Ideone sogar denselben Compiler verwendet wie ich. Und bei mir klappt es. Daher: Finger weg...
Das funktioniert nur deswegen, weil die Memberfunktionbefore()bei einigen Compilern die Vererbung berücksichtigt.
-
Der Code den du da aktuell hast kann nichtmal ansatzweise funktionieren.
Denk an Mehrfachvererbung und virtuelle Vererbung.
Und natürlich braucht man auch definiertes Verhalten für den Fall dass der Zeiger nicht konvertierbar ist.
-
Was ich dazu weiss: http://www.cplusplus.com/forum/articles/18756/
Die Möglichkeit relativ unkompliziert über Basisklassen auf Objekte in Type-Erasure-Containern
Das ist wiederspruechlich: An den Typ/Basistyp von Objekten zu gelangen, deren Typ geloescht ist. Warum sollte ich dann im Vorfeld type erasure anwenden? Auch ist mir unklar, welches Problem geloest werden soll. Nein, "öfters mal nützlich sein" hatte ich noch nie, wobei das kein wirkliches Kriterium ist
Type-Erasure-Containern
Was das sind, weiss ich nicht. Container mit type erased objecten? Dann spielen nur type erased objecte 'ne Rolle und der Container ist unwichtig.
Und warum bei Sone neuerdings alles
friendsein muss , ist mir auch unklar.
-
knivil schrieb:
Und warum bei Sone neuerdings alles
friendsein muss , ist mir auch unklar.Wenn du den Code nicht verstehst, dann verschwende ich nicht meine Zeit damit in dir zu erklären. Soviel sei gesagt: Die Factory braucht Zugriff auf den privaten Konstruktor. Diesen public zu machen, um deinen Ansprüchen genüge zu tun, würde dem User erlauben any_ptr irgendwie zu konstruieren... völliger Blödsinn.
-
Wenn du den Code nicht verstehst, dann verschwende ich nicht meine Zeit damit in dir zu erklären.
Und du laber mich nicht dumm von der Seite an, auch die friends in deinem Iteratorbeispiel konnten problemlos entfernt weden. Hoehenfluege?
-
knivil schrieb:
Wenn du den Code nicht verstehst, dann verschwende ich nicht meine Zeit damit in dir zu erklären.
Und du laber mich nicht dumm von der Seite an, auch die friends in deinem Iteratorbeispiel konnten problemlos entfernt weden. Hoehenfluege?
Gut, gut, gut. Tut mir Leid. Erkläre mir bitte, wie man das
friendentfernen kann, ohne die Syntax beim Aufruf zu verändern oder die Konstruktorenpubliczu machen oder sonst irgendwas.
Ich sehe keinen Weg. Und
friendist nur ein Schlüsselwort. Solange der User eine Funktionmake_any_ptraufrufen kann, undany_ptrprivateKonstruktoren hat, ist doch alles im Lot.
-
Kurze Erklaerung: Es gibt gewisse Schluesselreize, die Gefahr bedeuten, beispielsweise ist "new". Gleiches gilt fuer "friend". Selbst brauche ich es sehr wenig. Bis jetzt 2 Mal insgesamt. Waehrend fuer "new" direkte Loesungsmoeglichkeiten bestehen, ist "friend" eher ein konzeptionelles Problem. Und bevor ich das angehe, wuerde ich gern wissen, warum man gerne den Typ eines type erased objects haben moechte, bzw. warum man dann in erster Linie ueberhaupt type erasure nutzt. Bei boost::variant erhaelt man das mit 'nem Visitor, ist wohl aber nicht das gesuchte.
-
@knivil
Ein Type Erasure Container ist sowas wieboost::any.Und die Forderung an den Typ zu kommen ist überhaupt nicht widersprüchlich - das macht man bei Type-Erasure im Prinzip immer. Nur dass es meist hinter spezialisierten privaten Hilfs-Klassentemplates versteckt wird.
Beispiel für wo so etwas nützlich sein könnte wäre eine Registry für Objekte beliebigen Typs. Denk an Dependency Injection Frameworks o.ä. Bzw. auch einfach als Erweiterung von boost::any o.ä.
Was ich nicht verstehe, ist warum bei Fragen wie diesen oft so patzige Antworten kommen. OK, du hast keine Verwendung dafür. Fein. Noted. Kann man aber auch freundlicher sagen.
Das selbe gilt für
friend.friendist ein recht nützlicher Bestandteil von C++, der dabei hilft die Kapselung zu verstärken. Natürlich kann man auch so arbeiten als ob esfriendnicht gäbe. Dann wird manfriendauch nicht brauchen. Oh Wunder. Was aber nicht heisst dass man dabei unbedingt den besten Code produziert.
-
Was ich nicht verstehe, ist warum bei Fragen wie diesen oft so patzige Antworten kommen.
Zeig sie mir bitte in meinem Eingangspost. Ansonsten: Fuer geegeben Aktion habe ich mich fuer gegeben Reaktion entschieden. An anderen Tagen haette ich mich anders entschieden.
OK, du hast keine Verwendung dafür. Fein. Noted. Kann man aber auch freundlicher sagen.
Habe ich dazugeschrieben, dass das kein Kriterium. Kann man freundlich sagen, habe mich aber fuer netral entschieden. Es ist definitive nicht unfreundlich. Und zu friend habe ich mich bereits geaessert.
Ein Type Erasure Container ist sowas wie boost::any
Ich dachte boost::any ist einfach nur ein besseres void*.
Und die Forderung an den Typ zu kommen ist überhaupt nicht widersprüchlich - das macht man bei Type-Erasure im Prinzip immer.
Und meine Erfahrung widerspricht dem, deswegen habe ich ja von meiner Erfahrung berichtet. Ich verwende Type erasure im klassischen Sinn.