P0137 は関数テンプレート std::launder
を導入し、共用体、寿命、ポインタに関するセクションで標準に多くの変更を加えています。
この論文が解決しようとしている問題は何なのでしょうか?私が注意しなければならない言語の変更点は何でしょうか?そして、私たちは何をlaunder
しているのでしょうか?
std::launder` は適切な名前ですが、何のためにあるのかを知っている場合のみです。これは メモリロンダリング を行うものです。
論文の中の例を見てみましょう。
struct X { const int n; };
union U { X x; float f; };
...
U u = {{ 1 }};
この文は集約の初期化を行い、U
の最初のメンバを {1}
で初期化します。
nは
const変数なので、コンパイラは
u.x.n` が 常に 1 であると自由に仮定することができます。
では、このようにするとどうなるでしょうか。
X *p = new (&u.x) X {2};
Xは些細なことなので、新しいオブジェクトを作成する前に古いオブジェクトを破壊する必要はありません。新しいオブジェクトの
n` メンバは 2 になります。
では、教えてください... u.x.n
は何を返すのでしょうか?
明らかな答えは2です。なぜなら、コンパイラは本当にconst
変数(単にconst&
ではなく、const
と宣言されたオブジェクト変数)は決して変化しない*と仮定することが許されているからです。しかし、私たちはそれを変えてしまったのです。
[basic.life]/8 には、新しく作られたオブジェクトに、古いオブジェクトの変数/ポインタ/参照を通してアクセスしても良い場合が明記されています。そして、const
メンバを持つことは、不適格な要因のひとつとされています。
では...どうすれば u.x.n
について適切に話すことができるのでしょうか?
メモリをロンダリングする必要があるんだ。
assert(*std::launder(&u.x.n) == 2); //Will be true.
マネーロンダリングは、あなたがどこからお金を手に入れたか追跡されるのを防ぐために使われます。メモリロンダリングは、あなたがオブジェクトをどこから入手したかをコンパイラが追跡するのを防ぐために使用され、したがって、もはや適用されないかもしれない任意の最適化を回避することを余儀なくされます。
もう一つの欠点は、オブジェクトの型を変更した場合です。std::launder` はここでも役に立ちます。
aligned_storage<sizeof(int), alignof(int)>::type data;
new(&data) int;
int *p = std::launder(reinterpret_cast<int*>(&data));
[basic.life]/8 によると、古いオブジェクトのストレージに新しいオブジェクトを割り当てた場合、古いオブジェクトへのポインタを介して新しいオブジェクトにアクセスすることはできません。launder` を使うと、この問題を回避することができます。