最終更新日時(UTC):
が更新

履歴 編集

class template
<type_traits>

std::aligned_storage(C++11)(C++23で非推奨)

namespace std {
  template <std::size_t Len,
            std::size_t Align = default-alignment>
  struct aligned_storage {
    using type = ;
  };                                                                   // (1) C++11

  template <std::size_t Len,
            std::size_t Align = default-alignment>
  using aligned_storage_t = typename aligned_storage<Len,Align>::type; // (2) C++14
}

この機能はC++23で非推奨となった。代わりにalignas(T) std::byte[sizeof(T)];を使用することを推奨する。

概要

アライメント調整された領域を作る。

要件

Lenが非ゼロであること。Alignは、得ようとしてる領域の要素型Tに対するalignof(T)と同じ大きさか、もしくはデフォルト値であること。

効果

  • aligned_storageは、領域サイズLen、アライメントAlignで調整した未初期化領域をメンバ型typeとして定義する。
  • メンバ型typeはPOD型となる(参照:is_pod)。

Alignのデフォルト値は、Lenよりも大きくない、最も厳格なアライメント要件を持つ、C++の何らかのオブジェクト型のアラインメント値。

非推奨の詳細 (C++23)

この機能は、いくつかの点で高いレベルの有害になりえる:

  • この機能を呼び出すことで未定義動作を引き起こす (この型はストレージを提供するわけではない)
  • 保証が正しくない (標準では、型が少なくとも要求された以上の大きさであることのみを要件としているだけで、上限サイズを要求できない)
  • APIが多くの理由で間違っている。そのためにこのAPIを使うために繰り返し同じ事前作業が必要になる。API設計が間違っている理由は以下:
    • ::typeの値にアクセスするためにreinterpret_castが必要となってしまう。これによってconstexprで使用できず、未定義動作を引き起こせてしまう
    • C++14で導入されたaligned_storage_tではなく誤ってaligned_storageを使用してしまい、その違いに気づきにくい (aligned_storage::typeを指定しなければならない)
    • 少なくともNバイト以上という指定はできるが、実際のサイズ (上限サイズ) を指定できないため、必要以上のメモリが使用される可能性がある

これらの問題はaligned_unionも同様だが、とくにaligned_storageでは以下のような問題がある:

  • テンプレートパラメータとして型をとらず、サイズをとってしまっている
    • この機能は非常に稀な状況を除いて、AlignパラメータはLenパラメータに対して決まった指定の仕方をする。第1テンプレート引数としてsizeof(T)を指定するのであれば、第2テンプレート引数にはalignof(T)を指定しなければならない。typename aligned_storage<sizeof(T), alignof(T)>::type
  • 第2テンプレートパラメータにデフォルト引数が設定されている
    • このテンプレートパラメータは本来必要ないだけでなく、デフォルト引数が付けられてしまっている。コンパイラがaligned_storage_t<sizeof(T)>という使い方をやめさせる方法はない。第2テンプレートパラメータは実装定義のデフォルト値をもつが、Tに対して十分な場合とそうでない場合がある。そのため、暗黙に不正確になることを許可してしまっている

Facebookのfolly::aligned_storage_for_tはこれらの問題を解決している。

この機能を以下のように置き換えることを推奨する:

template <typename T>
class MyContainer {
  // [...]
private:
- std::aligned_storage_t<sizeof(T), alignof(T)> t_buff;
+ alignas(T) std::byte t_buff[sizeof(T)];
  // [...]
};

スタック領域を使用するコンテナ実装の例 (C++11)

#include <iostream>
#include <type_traits>

template <class T, std::size_t Capacity>
class StackContainer {
  // 長さ:sizeof(T) * Count、
  // アライメント:alignof(T)
  // でアライメント調整された領域を作る
  typename
    std::aligned_storage<sizeof(T) * Capacity, alignof(T)>::type
  storage_;

  T* ptr_ = nullptr;
  std::size_t size_ = 0;

public:
  void push(T value)
  {
    // 未初期化領域に要素を作る
    if (size_ <= 0) {
      ptr_ = new (&storage_) T(value);
    }
    else {
      new (ptr_ + size_) T(value);
    }
    ++size_;
  }

  T& front()
  {
    return *ptr_;
  }

  ~StackContainer()
  {
    for (std::size_t i = 0; i < size_; ++i) {
      (ptr_ + i)->~T();
    }
  }
};

int main()
{
  StackContainer<int, 3> cont;
  cont.push(1);

  std::cout << cont.front() << std::endl;
}

出力

1

スタック領域を使用するコンテナ実装の例 (C++17)

#include <iostream>
#include <type_traits>
#include <new>

template <class T, std::size_t Capacity>
class StackContainer {
  // 長さ:sizeof(T) * Count、
  // アライメント:alignof(T)
  // でアライメント調整された領域を作る
  typename
    std::aligned_storage<sizeof(T) * Capacity, alignof(T)>::type
  storage_;

  std::size_t size_ = 0;

  T* data()
  {
    return std::launder(reinterpret_cast<T*>(&storage_));
  }

public:
  void push(T value)
  {
    // 未初期化領域に要素を作る
    new (data() + size_) T(value);
    ++size_;
  }

  T& front()
  {
    return *data();
  }

  ~StackContainer()
  {
    for (std::size_t i = 0; i < size_; ++i) {
      (data() + i)->~T();
    }
  }
};

int main()
{
  StackContainer<int, 3> cont;
  cont.push(1);

  std::cout << cont.front() << std::endl;
}

出力

1

バージョン

言語

  • C++11

処理系

  • Clang: 3.0
  • GCC: 4.5.4
  • Visual C++: 2008 (std::tr1), 2010, 2012, 2013, 2015
    • 2010までは、Alignのデフォルト実引数が定義されていない。
    • 2012からは、Alignのデフォルト実引数はstd::alignment_of<max_align_t>::valueと定義されている。
    • aligned_storage_tは2013から

参照