この項目は配置 new ( placement new または プレイスメント new)を使います。 C++17 では配置 new の reinterpret_cast は std::launder を使うべきですが、一部のコンパイラでは不具合があるらしいので、ビルドエラーが発生したら無理してとおさない方が良いと思います。
std::aligned_storage はスタック、ヒープ関係なくアラインメントがとれたデータの型を提供します。
template< std::size_t Len, std::size_t Align = /*default-alignment*/ > struct aligned_storage;
データ型は 「 std::aligned_storage<引数1,引数2>::type 」として使えます。
オブジェクトサイズが最大にして Len で、Align がアラインメントサイズになります。
Len の値としては sizeof 演算子の値を設定することが可能ですね。
そして Align は最大でも Len と同じサイズとなります。
またオブジェクトのアラインメント要件( alignment requirement )が Align の約数の場合に適用されるという制約があります。
アラインメント要件は難しそうに聞こえますが alignof 演算子や std::alignment_of を使えば取得できます。
Align 引数は特別な値にする理由が限りは alignof 等で取得した値を使えば良いと思いんじゃないかと思います。
では実装してみましょうかね。
main.cpp.
1 #include <type_traits> 2 #include <iostream> 3 4 struct alignas(64) Foo { 5 int v,w,x,y,z; 6 }; 7 8 int main() 9 { 10 std::cout << "size: " << sizeof(Foo) << '\n'; 11 std::cout << "alignment: " << alignof(Foo) << '\n'; 12 13 std::aligned_storage<sizeof(Foo),alignof(Foo)>::type data[1024]; 14 new (&data[0]) int; 15 std::cout << (void*)&data[0] << '\n'; 16 17 #if __cplusplus >= 201703L 18 Foo* foo = std::launder(reinterpret_cast<Foo*>(&data[0])); 19 foo->v = 100; 20 std::cout << foo << " : value = " << foo->v << '\n'; 21 std::launder(reinterpret_cast<Foo*>(&data[0]))->~Foo(); 22 #else 23 Foo* foo = reinterpret_cast<Foo*>(&data[0]); 24 foo->v = 100; 25 std::cout << foo << " : value = " << foo->v << '\n'; 26 reinterpret_cast<Foo*>(&data[0])->~Foo(); 27 #endif 28 29 return 0; 30 }
ビルドと実行結果.
$ g++ main.cpp -std=c++17 -Wall $ ./a.out size: 64 alignment: 64 0x7fffd1d052c0 0x7fffd1d052c0 : value = 100
このソースコードは所定のオブジェクトを 64 バイトのキャッシュラインに合わせる前提で書いています。
4 struct alignas(64) Foo { 5 int v,w,x,y,z; 6 };
Foo は 20 バイト のオブジェクトなので 32 バイトにアラインされそうですが、主導で 64 バイトにアラインメントするように alignas で指定してるわけです。
C++17 では以下のように std::launder を使って古いポインターのデータをロンダリングします。
17 #if __cplusplus >= 201703L 18 Foo* foo = std::launder(reinterpret_cast<Foo*>(&data[0])); 19 foo->v = 100; 20 std::cout << foo << " : value = " << foo->v << '\n'; 21 std::launder(reinterpret_cast<Foo*>(&data[0]))->~Foo();
C++17 以前の場合は以下のように筆者はしていました… (´・ω・`)
22 #else 23 Foo* foo = reinterpret_cast<Foo*>(&data[0]); 24 foo->v = 100; 25 std::cout << foo << " : value = " << foo->v << '\n'; 26 reinterpret_cast<Foo*>(&data[0])->~Foo(); 27 #endif
まあ std::launder も無いですしね…
グ… 実はゲロりますと C++17 以前にこの構文を使っていたことを後悔しておりまして、読者は悪い例は学ばないようにしてくださいね。
一番の問題があるのは以下の箇所です。
13 std::aligned_storage<sizeof(Foo),alignof(Foo)>::type data[1024]; 14 new (&data[0]) int;
つまり 配置 new をするなら新たなポインターに代入すべきで古いポインターを再利用すること自体良くないような気がしています。
まあ筆者も絶対駄目と言ってるわけじゃないんですが、読み手の方にご自分で判断して頂くしかないのかも… しれないです…
(´・ω・`)
Copyright 2018-2019, by Masaki Komatsu