std::launder
ja.cppreference.net より
| ヘッダ <new>
|
||
template< class T >
constexpr T* launder( T* p ) noexcept;
|
で定義 (C++17以上) | |
pに関する非仮想化フェンス。pが表すアドレスにあるオブジェクトを指すポインタを返します。ただし、そのオブジェクトは新しい基底クラスのサブオブジェクトであり、その最も派生したクラスが元の *p オブジェクトとは異なる可能性があります。
形式的には、以下の条件を満たす場合
- ポインタ
pがメモリ内のバイトのアドレスAを表す - オブジェクト
xがアドレスA xに位置し、かつその生存期間内にあるxの型がTと同じである(すべてのレベルでのcv修飾子を無視する)- 結果を介して到達可能なすべてのバイトが p を介して到達可能である(バイトは、オブジェクト
yを指すポインタを介して到達可能であり、それらのバイトがzとポインタ相互変換可能なオブジェクトyのストレージ内にある場合、またはzが要素である直接の外側の配列内にある場合)。
その場合、std::launder(p) はオブジェクト T* を指す x 型の値を返します。そうでなければ、動作は未定義です。
プログラムは、T が関数型または(cv修飾されている可能性がある)void である場合、不正形式です。
std::launder は、その引数の(変換された)値を関数呼び出しの代わりに使用できる場合にのみ、コア定数式で使用できます。言い換えれば、std::launder は定数評価において制限を緩和しません。
注記
std::launder
はその引数に影響を与えません。オブジェクトにアクセスするには、その戻り値を使用しなければなりません。したがって、戻り値を破棄することは常にエラーとなります。
std::launderの典型的な使用例:
- 既存の同一型オブジェクトのストレージ内で作成されたオブジェクトへのポインタを取得する場合(古いオブジェクトへのポインタを 再利用できない 状況、例えばどちらかのオブジェクトが基底クラスの部分オブジェクトである場合など);
-
プレースメント
newによって作成されたオブジェクトへのポインタを、そのオブジェクトにストレージを提供するオブジェクトへのポインタから取得する場合。
reachability
制約は、
std::launder
が元のポインタを通じてアクセス可能でないバイトへのアクセスに使用できないことを保証し、これによりコンパイラのエスケープ解析を妨げることを防ぎます。
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])); // 未定義動作: 結果のポインタからx2[1]に到達可能となるが、ソースからは到達不能 // 元のソースからは到達不能 struct X { int a[10]; } x3, x4[2]; // 標準レイアウト; パディングなしと仮定 auto p3 = std::launder(reinterpret_cast<int(*)[10]>(&x3.a[0])); // OK auto p4 = std::launder(reinterpret_cast<int(*)[10]>(&x4[0].a[0])); // 未定義動作: 結果のポインタからx4[1]に到達可能となるが(これはx4[0]とポインタ相互変換可能) // 元のソースからは到達不能 struct Y { int a[10]; double y; } x5; auto p5 = std::launder(reinterpret_cast<int(*)[10]>(&x5.a[0])); // 未定義動作: 結果のポインタからx5.yに到達可能となるが // 元のソースからは到達不能
例
このコードを実行
#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() { // ケース1: 新しいオブジェクトが基底サブオブジェクトであるが、古いオブジェクトが完全オブジェクトであるため、透過的に置換可能ではない Base base; int n = base.transmogrify(); // int m = base.transmogrify(); // 未定義動作 int m = std::launder(&base)->transmogrify(); // OK assert(m + n == 3); // ケース2: バイト配列を通じて提供されるストレージを持つ新しいオブジェクトへの配列へのポインタによるアクセス 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; // クラスメンバアクセスは未定義動作: reinterpret_cast<Y*>(&s) // の値は「sへのポインタ」であり、Yオブジェクトを指していない const int g = q->z; // OK const int h = std::launder(reinterpret_cast<Y*>(&s))->z; // OK [](...){}(f, g, h); // [[maybe_unused]] 効果を発動 }
欠陥報告
以下の動作変更の欠陥報告書は、以前に公開されたC++規格に対して遡及的に適用されました。
| DR | 適用対象 | 公開時の動作 | 正しい動作 |
|---|---|---|---|
| LWG 2859 | C++17 |
到達可能の定義がポインタ相互変換可能なオブジェクトからの
ポインタ演算を考慮していなかった |
含まれるよう修正 |
| LWG 3495 | C++17 |
std::launder
が定数式内で非アクティブなメンバへの
ポインタをデリファレンス可能にする可能性があった |
禁止 |