std::launder
| Definiert im Header <new> |
||
| template< class T > constexpr T* launder( T* p ) noexcept; |
(seit C++17) | |
Devirtualisierungs-Zaun (fence) in Bezug auf p. Gibt einen Zeiger auf ein Objekt an derselben Adresse zurück, die p darstellt, wobei das Objekt ein neues Basiklassen-Subobjekt sein kann, dessen am höchsten abgeleitetes Objekt von dem des ursprünglichen *p-Objekts abweicht.
Formell, gegeben
- der Zeiger p stellt die Adresse
Aeines Bytes im Speicher dar - ein Objekt x befindet sich an der Adresse
A - x befindet sich innerhalb seiner Lebensdauer
- der Typ von x ist derselbe wie
T, wobei cv-Qualifizierer auf jeder Ebene ignoriert werden - jedes Byte, das über das Ergebnis erreichbar wäre, ist über p erreichbar (Bytes sind über einen Zeiger erreichbar, der auf ein Objekt y zeigt, wenn diese Bytes sich innerhalb des Speichers eines Objekts z befinden, das zu y zeiger-konvertierbar ist, oder innerhalb des unmittelbar umschließenden Arrays, dessen Element z ist).
Dann gibt std::launder(p) einen Wert vom Typ T* zurück, der auf das Objekt x zeigt. Andernfalls ist das Verhalten undefiniert.
Das Programm ist schlecht formuliert, wenn T ein Funktionstyp oder (möglicherweise cv-qualifiziertes) void ist.
std::launder darf in einem Kern-Konstantausdruck verwendet werden, wenn und nur wenn der (konvertierte) Wert seines Arguments anstelle des Funktionsaufrufs verwendet werden kann. Mit anderen Worten, std::launder lockert die Einschränkungen bei der Konstantenauswertung nicht.
[bearbeiten] Anmerkungen
std::launder hat keine Auswirkung auf sein Argument. Sein Rückgabewert muss verwendet werden, um auf das Objekt zuzugreifen. Daher ist es immer ein Fehler, den Rückgabewert zu verwerfen.
Typische Verwendungen von std::launder umfassen
- Einen Zeiger auf ein Objekt zu erhalten, das im Speicher eines vorhandenen Objekts desselben Typs erstellt wurde, wobei Zeiger auf das alte Objekt nicht wiederverwendet werden können (zum Beispiel, weil ein Objekt ein Basiklassen-Subobjekt ist);
- Einen Zeiger auf ein Objekt zu erhalten, das durch Placement
newaus einem Zeiger auf ein Objekt erstellt wurde, das Speicher für dieses Objekt bereitstellt.
Die *Erreichbarkeits*-Einschränkung stellt sicher, dass std::launder nicht verwendet werden kann, um auf Bytes zuzugreifen, die nicht über den ursprünglichen Zeiger erreichbar sind, und somit die Flucht-Analyse des Compilers nicht stört.
int x[10]; auto p = std::launder(reinterpret_cast<int(*)[10]>(&x[0])); // OK int x2[2][10]; auto p2 = std::launder(reinterpret_cast<int(*)[10]>(&x2[0][0])); // Undefined behavior: x2[1] would be reachable through the resulting pointer to x2[0] // but is not reachable from the source struct X { int a[10]; } x3, x4[2]; // standard layout; assume no padding auto p3 = std::launder(reinterpret_cast<int(*)[10]>(&x3.a[0])); // OK auto p4 = std::launder(reinterpret_cast<int(*)[10]>(&x4[0].a[0])); // Undefined behavior: x4[1] would be reachable through the resulting pointer to x4[0].a // (which is pointer-interconvertible with x4[0]) but is not reachable from the source struct Y { int a[10]; double y; } x5; auto p5 = std::launder(reinterpret_cast<int(*)[10]>(&x5.a[0])); // Undefined behavior: x5.y would be reachable through the resulting pointer to x5.a // but is not reachable from the source
[bearbeiten] Beispiel
#include <cassert> #include <cstddef> #include <new> struct Base { virtual int transmogrify(); }; struct Derived : Base { int transmogrify() override { new(this) Base; return 2; } }; int Base::transmogrify() { new(this) Derived; return 1; } static_assert(sizeof(Derived) == sizeof(Base)); int main() { // Case 1: the new object failed to be transparently replaceable because // it is a base subobject but the old object is a complete object. Base base; int n = base.transmogrify(); // int m = base.transmogrify(); // undefined behavior int m = std::launder(&base)->transmogrify(); // OK assert(m + n == 3); // Case 2: access to a new object whose storage is provided // by a byte array through a pointer to the array. struct Y { int z; }; alignas(Y) std::byte s[sizeof(Y)]; Y* q = new(&s) Y{2}; const int f = reinterpret_cast<Y*>(&s)->z; // Class member access is undefined // behavior: reinterpret_cast<Y*>(&s) // has value "pointer to s" and does // not point to a Y object const int g = q->z; // OK const int h = std::launder(reinterpret_cast<Y*>(&s))->z; // OK [](...){}(f, g, h); // evokes [[maybe_unused]] effect }
[bearbeiten] Fehlerberichte
Die folgenden Verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.
| DR | angewendet auf | Verhalten wie veröffentlicht | Korrigiertes Verhalten |
|---|---|---|---|
| LWG 2859 | C++17 | Definition von *erreichbar* berücksichtigte keinen Zeiger Arithmetik von zeiger-konvertierbaren Objekten |
enthalten |
| LWG 3495 | C++17 | std::launder könnte Zeiger auf einen inaktivenMitglied-Dereferenzierung in einem Konstantausdruck |
verbieten |