Placement new in generic union class
-
Hi,
ich habe eine Frage zu "placement new" feature.
Ich möchte kleine Datentypen (<= 8 bytes) in einer union speichern mit "placement new". Bei größeren Datentypen verwende ich das "normal" new und merke mir nur den Pointer.union Variant { bool b; signed char sc; unsigned char uc; char c; wchar_t wc; short int si; unsigned short int usi; int i; unsigned int ui; long int li; unsigned long int uli; long long int lli; unsigned long long int ulli; float f; double d; long double ld; void *custom_data; }; struct Foo { int i = 12; bool flag = true; }; template<typename T, typename Enable = void> struct Policy { static void copy(void* src, Variant& dest) { dest.custom_data = new T(*reinterpret_cast<T*>(src)); std::cout << "create new" << std::endl; } }; template<typename T> struct Policy<T, typename std::enable_if<std::is_trivially_copyable<T>::value && sizeof(T) <= sizeof(Variant)>::type> { static void copy(void* src, Variant& dest) { new(&dest.custom_data) T(*reinterpret_cast<T*>(src)); std::cout << "place new" << std::endl; } }; int main() { Variant data; Foo obj; obj.i = 42; Policy<Foo>::copy(&obj, data); obj.i = 23; return 0; }http://melpon.org/wandbox/permlink/fd7KrrXHh5pEk38h
Meine Frage lautet:
1)
Ist das gültiger C++ Code oder verstoße ich damit gegen irgendwelche C++ alignment regeln?
Wie ich gelesen habe verwendet man eigentlich immerstd::aligned_storagemit placement new, aber das kann ich ja nicht, da ich die zu speichernden Datentypen ja noch nicht kenne.Wenn das gültiger code, ist meine Annahme dann richtig, dass ich "placement new" so wie ich es hier verwende nur für Objekte anwenden darf die trivial kopierbar sind?
-
- Keine Ahnung. Ich sehe aber keinen Grund
aligned_storagenicht zu verwenden.
struct Variant { static size_t const Size = max({ sizeof(int), sizeof(long), sizeof(double), sizeof(long double), ... }); static size_t const Align = max({ alignof(int), alignof(long), alignof(double), alignof(long double), ... }); aligned_storage<Size, Align>::type m_storage; };- In deinem konkreten Beispiel sehe ich kein Problem mit nicht trivial kopierbaren Objekten. Du kopierst den
Variantja nichmal irgendwo. Und ich vermute mal dass du ihn sogar kopieren dürftest. Ein Problem ergibt sich erst wenn du den Storage der Kopie dann als (nicht trivial zu kopierendes)TObjekt behandelst. Weil "in" der Kopie dann einfach keinTObjekt ist. Sondern bloss einaligned_storage<Size, Align>voller "Bytes".
Wenn du es allerdings so machst...
int main() { NonTrivialCopy obj; obj.i = 42; Variant data; Policy<NonTrivialCopy>::copy(&obj, data); Variant data2 = data; // Macht nix sinnvolles, aber auch nix schlimmes Policy<NonTrivialCopy>::copy(&data.m_storage, data2); // Jetzt steckt in data2 auch ein "NonTrivialCopy" return 0; }...müsste es mMn. wieder OK sein.
- Keine Ahnung. Ich sehe aber keinen Grund
-
Das wird natürlich noch alles gekapselt, ich hab es nur für meine Frage so einfach wie möglich runter gebrochen. Das Variant wird natürlich noch kopierbar sein. Den Code habe ich aber entfernt.
Du hast gesehen das
Variantkeine struct ist, sondern ein Union?
Wozu brauch ich dann nochstd::aligned_storage?In deinem konkreten Beispiel sehe ich kein Problem mit nicht trivial kopierbaren Objekten.
Du siehst zusätzlich kein Problem mit nicht trivial kopierbaren Objekten? Oder hast du dich da verschrieben?
-
Nash26 schrieb:
In deinem konkreten Beispiel sehe ich kein Problem mit nicht trivial kopierbaren Objekten.
Du siehst zusätzlich kein Problem mit nicht trivial kopierbaren Objekten? Oder hast du dich da verschrieben?
Effektiv ruft placement new ja nur den Konstruktor auf und in deinem Fall rufst du den copy Konstruktor auf. Es sollte also auch mit nicht trivial kopierbaren Objekten funktionieren. Bei trivial kopierbaren Objekten könntest du eigentlich auch ein
memcpy(&dest.custom_data, src, sizeof(T));machen.
-
Hier wird das Thema kurz erwähnt
https://isocpp.org/wiki/faq/dtors#placement-newDANGER: You are taking sole responsibility that the pointer you pass to the “placement new” operator points to a region of memory that is big enough and is properly aligned for the object type that you’re creating. Neither the compiler nor the run-time system make any attempt to check whether you did this right. If your Fred class needs to be aligned on a 4 byte boundary but you supplied a location that isn’t properly aligned, you can have a serious disaster on your hands (if you don’t know what “alignment” means, please don’t use the placement new syntax). You have been warned.
Ich bin mit dem alignment aber noch nicht 100% vertraut, was bewirkt dieses:
aligned_storageintern?
Verschiebt es die Startadresse des Objektes, so das sie immer module 0 ergibt?
-
Nash26 schrieb:
- (...)
Du hast gesehen dasVariantkeine struct ist, sondern ein Union?
Wozu brauch ich dann nochstd::aligned_storage?
Du hast gelesen dass ich "1) Keine Ahnung. Ich sehe aber keinen Grund aligned_storage nicht zu verwenden." geschrieben habe

Nash26 schrieb:
In deinem konkreten Beispiel sehe ich kein Problem mit nicht trivial kopierbaren Objekten.
Du siehst zusätzlich kein Problem mit nicht trivial kopierbaren Objekten? Oder hast du dich da verschrieben?
Ich denke ich hab' es schon ausreichend beschrieben.
Du kannst da mit placement new ein T reinmachen was nicht trivial kopierbar ist.
Du kannst dann weiterhin das Variant Ding mit dem auto-generierten Ctor kopieren.
Nur dann ist in der trivial kopierten Kopie natürlich kein T drinnen.
Demzufolge kannst du es dann auch nicht als T ansprechen.
Wenn du aber wiederrum mit placement new kopierst, dann sollte es auch OK sein.Guck dir z.B. einfach mal die Implementierung von
boost::optionalan.
- (...)
-
Ok, und was ist wenn ich folgendes Objekt in die union packe?
struct alignas(128) Fred { int i; };Ich denke das sollte dann wirklich Probleme geben, was auch immer das heißen mag? Crash? Langsamer Daten zugriff?
Ich formuliere meine Frage allgemeiner:
Was ist die Bedingung dafür, dass ich eine beliebigen Klasse auf diese union (die alle primitive Datentypen beinhaltet) mit
placement newabspeichern kann?Muss ich den Code auf dein spezielles Variant ändern mit
aligned_storage?
oder kann ich meine Union weiter verwenden?In welchen Kapitel im Standard finde ich dazu eine Information? Unter dem Punkt 9.5 Unions ist jedenfalls nichts.
-
Nash26 schrieb:
Ok, und was ist wenn ich folgendes Objekt in die union packe?
struct alignas(128) Fred { int i; };Ich denke das sollte dann wirklich Probleme geben, was auch immer das heißen mag? Crash? Langsamer Daten zugriff?
Soweit ich weiss ist
alignof > sizeofnicht möglich.
Würde zumindest nie mit Arrays funktionieren - wie sollte sonst jemals das Alignment des 2. Elements passen?Nash26 schrieb:
Ich formuliere meine Frage allgemeiner:
Was ist die Bedingung dafür, dass ich eine beliebigen Klasse auf diese union (die alle primitive Datentypen beinhaltet) mit
placement newabspeichern kann?Muss ich den Code auf dein spezielles Variant ändern mit
aligned_storage?
oder kann ich meine Union weiter verwenden?Du musst vermutlich nicht, aber ich würde es machen. Weil ich mit
aligned_storageweiss was ich bekomme, weiss wie man es verwenden muss.Und was sind die Bedingungen?
Na die Grösse muss passen und das Alignment muss passen.Ganze einfache Variante: verwende
aligned_storage<N, N>::type, wobei du dir N einfach frei aussuchst -- so viel wie du halt maximal für nen char/bool "verschwenden" willst. Dann musst du nur noch die Grösse prüfen, daalignof > sizeofeben nicht geht.Nash26 schrieb:
In welchen Kapitel im Standard finde ich dazu eine Information? Unter dem Punkt 9.5 Unions ist jedenfalls nichts.
Kann ich dir nicht auswendig sagen. Vermutlich aber verstreut über 100 Paragraphen.
-
Aktuell is meine Bedingung:
template<typename T> using can_place_in_variant = std::integral_constant<bool, sizeof(T) <= sizeof(variant) && alignof(T) == alignof(variant)>;Primitive datentypen (int, bool) wollte ich halt direkt in der union abspeichern ohne "placement new", da brauch ich mich nicht kompliziert zu casten um den Wert da herauszubekommen und es wird vielleicht auch ein wenig was kosten.
-
alignof(T) == alignof
alignof(T) <= alignofSieht sonst OK aus.
Und primitive Typen kannst du auch einfach per gecastetem Zeiger da rein schreiben bzw. wieder rauslesen.
-
hustbaer schrieb:
alignof(T) <= alignof
alignof(variant) % alignof(T) == 0?
-
@Arcoth
Ich hab in Erinnerung dass als Alignment Requirement eh nur Zweierpotenzen erlaubt sind.
Von daher sollte der "kleiner" Vergleich ausreichend sein.Falls das nicht stimmt muss man natürlich % verwenden, ja.
-
Ich hab in Erinnerung dass als Alignment Requirement eh nur Zweierpotenzen erlaubt sind.
Stimmt natürlich. §3.11/4.
Das ist echt clever von dir. Leider ist cleverer Code der die Intention nicht ausdrückt.. mMn. allgemein nicht zu empfehlen. Genauso würde ich niemalsif (a & 2)schreiben um auszudrücken, dass
aeine gerade Ganzzahl ist, nur weil der Standard festlegt, dass nur Ganzzahlrepresentierungen verwendet werden dürfen die derartiges unterstützen.
Ich möchte mich in Code so ausdrücken dass jeder versteht welcher Gedanke hinter einer Bedingung steht. Und deine Art es zu schreiben impliziert einfach, dass kein weiteres Verhältnis zwischen den Ausrichtungen bestehen muss, als dass die von T kleiner als die von X ist.Sag's mir, falls ich übertreibe.
-
Arcoth schrieb:
Leider ist cleverer Code der die Intention nicht ausdrückt.. mMn. allgemein nicht zu empfehlen.
Arcoth@unverspielt:

Hast anscheinend die 15 Jahre, die die meisten dafür brauchen, auf nur 5 drücken können.
Hoffe, ich hatte mit meiner Meckerei einen kleinen Anteil (vermutlich dadurch, daß Du genau das Gegenteil meiner Wünsche machtest und gegen Wände gelaufen bist).
-
@Arcoth
Ich verstehe dein Argument.
Ich weiss nicht ob ich sagen würde dass du übertreibst. Wenn der einzige Nachteil deiner Variante der wäre, dass sie ein klitzekleines bisschen "komplizierter" ist, dann würde ich dir vielleicht zustimmen.Nur ... genau so wie meine Variante impliziert dass a <= b ausreicht, und nicht noch die versteckte Bedingung dass a und b beides Zweierpotenzen sein müssen notwendig ist...
Genau so impliziert deine Variante dass es nötig ist den Test b % a == 0 zu machen, und dass es eben keine weitere Vorschrift vom Standard gibt die einem das Leben hier etwas einfacher macht.Wenn ein armer Programmierer das jetzt nicht weiss, und deinen Code liest, und das Programm erweitern muss...
und dann davon ausgeht dass er überall mit allen möglichen Alignment Requirements rechnen muss...
und nicht auf die Idee kommt mal im Standard nachzugucken was da wirklich die Regeln sind...
dann würde er vermutlich viel viel zu komplizierten Code schreiben.Was mMn. klar gegen deine Variante spricht.
Aber Gott-sei-Dank gibt es ja sowas wie Kommentare

Vorschlag:alignof(T) <= alignof(variant) /* alignment requirements are always a power of two, so checking a <= b is sufficient */oder, wenn du unbedingt willst
alignof(variant) % alignof(T) == 0 /* actually alignment requirements are always a power of two, so b <= a would suffice, but a % b == 0 more clearly expresses what we are really testing here */Wobei ich auch hier die a <= b Variante vorziehe, weil sie den Fokus auf die einfachere, ausreichende Variante setzt.
ps:
Arcoth schrieb:
Stimmt natürlich. §3.11/4.
Das ist echt clever von dir.Gar nicht clever, ich kenne es einfach nicht anders. Also jeglicher Code den ich bisher gesehen habe der Alignment Requirements prüft verwendet einfach a <= b Tests.
BTW: Nachdem sich bei C++ in den letzten Jahren einiges getan hat, und auch hoffentlich weiterhin was tut, und so viel tut dass sich die ganzen §-Nummern im Standard gröber verschieben, wäre es vielleicht günstig immer die Standard-Version mit anzugeben.
Also entweder die "n-Nummer" oder einfach "C++XY".EDIT: Fixed: a < b --> a <= b
-
volkard schrieb:
Hast anscheinend die 15 Jahre, die die meisten dafür brauchen, auf nur 5 drücken können.
Ich finde die Entwicklung von HaSoCoth ... erstaunlich. Ernsthaft.
@Arcoth
-
volkard schrieb:
Hast anscheinend die 15 Jahre, die die meisten dafür brauchen, auf nur 5 drücken können.
Vier

-
Arcoth schrieb:
Genauso würde ich niemals
if (a & 2)schreiben um auszudrücken, dass
aeine gerade Ganzzahl ist, nur weil der Standard festlegt, dass nur Ganzzahlrepresentierungen verwendet werden dürfen die derartiges unterstützen.würde ich auch nicht, aber aus einem anderen Grund.
(Tip: !(a&1))
-
Wenn ein armer Programmierer das jetzt nicht weiss, und deinen Code liest, und das Programm erweitern muss...
und dann davon ausgeht dass er überall mit allen möglichen Alignment Requirements rechnen muss...Bin nicht dran gewohnt oder ausgerichtet mit Leuten zu arbeiten, die unbegründete Annahmen aus einer Code-Zeile entnehmen und auf dieser Basis ein Projekt erweitern/umschreiben.
großbuchstaben schrieb:
würde ich auch nicht, aber aus einem anderen Grund.
(Tip: !(a&1))Ups, da war ich im Eifer des Gefechts ganz woanders, sorry. Es hilft nicht, dass ich gerade mit etwas völlig anderem Beschäftigt bin.
hustbaer schrieb:
volkard schrieb:
Hast anscheinend die 15 Jahre, die die meisten dafür brauchen, auf nur 5 drücken können.
Ich finde die Entwicklung von HaSoCoth ... erstaunlich. Ernsthaft.
@ArcothKlingt zynisch. Und da ich annehmen darf, dass ein Gentlemen wie du nicht aus Versehen zynisch ist...
-
Daran war nichts zynisch gemeint.
Du musst ja auch selbst merken wie sich z.B. der Ton mit dem wir uns unterhalten MASSIV verbesser hat in den letzten Jahren.ps: Falls du dich am "HaSoCoth" gestossen hast - das war nicht böse gemeint. Der kleine Insiderwitz sei mir bitte verziehen

-
Arcoth schrieb:
Wenn ein armer Programmierer das jetzt nicht weiss, und deinen Code liest, und das Programm erweitern muss...
und dann davon ausgeht dass er überall mit allen möglichen Alignment Requirements rechnen muss...Bin nicht dran gewohnt oder ausgerichtet mit Leuten zu arbeiten, die unbegründete Annahmen aus einer Code-Zeile entnehmen und auf dieser Basis ein Projekt erweitern/umschreiben.
Das selbe Argument funktioniert auch für den Standpunkt dass deine Variante unnötig kompliziert ist, und auch ein einfaches a <= b ohne weiteres Kommentar völlig ausreicht.
Wenn ich deinen Code - ohne weiteres Kommentar - irgendwo lesen würde, dann würde es mich kurz verwirren. Und danach würde ich mir denken "weiss der nicht dass a <= b reicht, oder macht er Dinge einfach gerne unnötig kompliziert?".