std:: launder
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Functions | ||||
|
(C++11)
|
||||
| Classes | ||||
|
(C++11)
|
||||
|
(C++17)
|
||||
| Types | ||||
| Objects | ||||
|
(C++20)
|
||||
| Object access | ||||
|
launder
(C++17)
|
|
Definiert im Header
<new>
|
||
|
template
<
class
T
>
constexpr T * launder ( T * p ) noexcept ; |
(seit C++17) | |
Devirtualisierungs-Barriere in Bezug auf p . Gibt einen Zeiger auf ein Objekt an derselben Adresse zurück, die p repräsentiert, wobei das Objekt ein neues Basisklassen-Subobjekt sein kann, dessen am meisten abgeleitete Klasse sich von der des ursprünglichen * p -Objekts unterscheidet.
Formal gegeben
-
der Zeiger
p
repräsentiert die Adresse
Aeines Bytes im Speicher -
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 durch das Ergebnis erreichbar wäre, ist durch p erreichbar (Bytes sind durch einen Zeiger erreichbar, der auf ein Objekt y zeigt, wenn diese Bytes innerhalb des Speichers eines Objekts z liegen, das zeigerinterkonvertierbar mit y ist, oder innerhalb des unmittelbar umgebenden Arrays, von dem z ein Element 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 fehlerhaft, wenn
T
ein Funktionstyp oder (möglicherweise cv-qualifiziertes)
void
ist.
std::launder
kann in einem
Core Constant Expression
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.
Hinweise
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 Anwendungen von
std::launder
umfassen:
- Erhalten eines Zeigers auf ein Objekt, das im Speicher eines bestehenden Objekts desselben Typs erstellt wurde, wobei Zeiger auf das alte Objekt nicht wiederverwendet werden können (beispielsweise weil eines der Objekte ein Basisklassen-Subobjekt ist);
-
Erhalten eines Zeigers auf ein Objekt, das durch Placement-
newerstellt wurde, aus einem Zeiger auf ein Objekt, das den Speicher für dieses Objekt bereitstellt.
Die
reachability
-Einschränkung stellt sicher, dass
std::launder
nicht verwendet werden kann, um auf Bytes zuzugreifen, die nicht durch den ursprünglichen Zeiger erreichbar sind, und stört dadurch die Escape-Analyse des Compilers.
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])); // Undefiniertes Verhalten: x2[1] wäre über den resultierenden Zeiger auf x2[0] erreichbar // ist aber von der Quelle aus nicht erreichbar struct X { int a[10]; } x3, x4[2]; // Standard-Layout; ohne Padding angenommen auto p3 = std::launder(reinterpret_cast<int(*)[10]>(&x3.a[0])); // OK auto p4 = std::launder(reinterpret_cast<int(*)[10]>(&x4[0].a[0])); // Undefiniertes Verhalten: x4[1] wäre über den resultierenden Zeiger auf x4[0].a // (der zeigerinterkonvertierbar mit x4[0] ist) erreichbar, ist aber von der Quelle aus nicht erreichbar struct Y { int a[10]; double y; } x5; auto p5 = std::launder(reinterpret_cast<int(*)[10]>(&x5.a[0])); // Undefiniertes Verhalten: x5.y wäre über den resultierenden Zeiger auf x5.a // erreichbar, ist aber von der Quelle aus nicht erreichbar
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() { // Fall 1: Das neue Objekt konnte nicht transparent ersetzt werden, weil // es ein Basis-Subobjekt ist, aber das alte Objekt ein vollständiges Objekt ist. Base base; int n = base.transmogrify(); // int m = base.transmogrify(); // undefiniertes Verhalten int m = std::launder(&base)->transmogrify(); // OK assert(m + n == 3); // Fall 2: Zugriff auf ein neues Objekt, dessen Speicher // durch ein Byte-Array bereitgestellt wird, über einen Zeiger auf das 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; // Klassenmember-Zugriff ist undefiniertes // Verhalten: reinterpret_cast<Y*>(&s) // hat den Wert "Zeiger auf s" und zeigt // nicht auf ein Y-Objekt const int g = q->z; // OK const int h = std::launder(reinterpret_cast<Y*>(&s))->z; // OK [](...){}(f, g, h); // erzeugt [[maybe_unused]]-Effekt }
Fehlerberichte
Die folgenden verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.
| DR | Angewendet auf | Verhalten wie veröffentlicht | Korrektes Verhalten |
|---|---|---|---|
| LWG 2859 | C++17 |
Definition von
reachable
berücksichtigte keine Pointer-Arithmetik
von pointer-interconvertiblem Objekt |
eingeschlossen |
| LWG 3495 | C++17 |
std::launder
könnte Pointer auf inaktives
Mitglied in konstantem Ausdruck dereferenzierbar machen |
verboten |