std:: launder
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Functions | ||||
|
(C++11)
|
||||
| Classes | ||||
|
(C++11)
|
||||
|
(C++17)
|
||||
| Types | ||||
| Objects | ||||
|
(C++20)
|
||||
| Object access | ||||
|
launder
(C++17)
|
|
定義先ヘッダ
<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 を指すポインタを通じて到達可能である。それらのバイトが y と ポインタ相互変換可能 なオブジェクト z のストレージ内にある場合、または z が要素である直近の囲み配列内にある場合)。
この場合、
std
::
launder
(
p
)
はオブジェクト
x
を指す
T*
型の値を返します。それ以外の場合、動作は未定義です。
T
が関数型または(修飾された可能性のある)
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
が定数式内で非アクティブなメンバへの
ポインタをデリファレンス可能にする可能性があった |
禁止 |