第56章 std::aligned_storage ( C++11 )

注記

この項目は配置 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