株式会社エス・スリー・フォー

初期化されていないメモリへの記憶

── 標準C++ライブラリ : <memory> ──

C++が標準で用意しているメモリ確保は、operator newです。

X* p = new X[N];によって、XのインスタンスをN個、連続した領域に構築します。

しかしながら、たとえば上記のXがデフォルト・コンストラクタを持たない場合、new X[N] はコンパイル・エラーとなります。

あるいはmallocで取得した領域のように初期化されていないメモリにオブジェクトを生成したい場合、単に代入(operator =)してはいけません。代入は初期化された領域の内容を変更する操作であり、初期化されていない領域に対して代入を行なってはなりません。

  X* p = reinterpret_cast<X*>(malloc(sizeof(X)));
  *p = X(100); // やってはいけない!

このようなシチュエーションに対応するため、標準C++ライブラリにはいくつかの'初期化されていないメモリ'に対する操作クラス/関数が、ヘッダ <memory> に用意されています。

raw_storage_iterator

template <class OutputIterator, class T>
class raw_storage_iterator
    : public iterator<output_iterator_tag,void,void,void,void> {
public:
  explicit raw_storage_iterator(OutputIterator x);
  raw_storage_iterator<OutputIterator,T>& operator*();
  raw_storage_iterator<OutputIterator,T>& operator=(const T& element);
  raw_storage_iterator<OutputIterator,T>& operator++();
  raw_storage_iterator<OutputIterator,T>  operator++(int);
};
  • raw_storage_iterator(OutputIterator x);
    xが指すのと同じ値を示すイテレータを初期化します。

  • raw_storage_iterator<OutputIterator,T>& operator*();
    *thisを返します。

  • raw_storage_iterator<OutputIterator,T>& operator=(const T& element);
    イテレータが指す場所に、elementを用いて値を生成します。

  • raw_storage_iterator<OutputIterator,T>&operator++();
    イテレータをひとつ進め、そのイテレータへの参照を返します。(前置 ++)

  • raw_storage_iterator<OutputIterator,T>operator++(int);
    イテレータをひとつ進めますが、返す値は進める前のイテレータの値です。(後置 ++)

一時バッファ

  • template<class T> pair<T*,ptrdiff_t> get_temporary_buffer(ptrdiff_t n);
    n個の連続した T型オブジェクトを格納するのに十分な記憶領域(バッファ)を確保します。
    戻り値は確保された領域のポインタと確保された個数からなる pair です。

  • template<class T> void return_temporary_buffer(T* p);
    get_temprary_bufferによって確保されたバッファpを解放します。

特殊化されたアルゴリズム

以下のアルゴリズムはplacement new 構文を利用して、初期化されていないメモリに対する初期化を行ないます。

  • template<class InputIterator, class ForwardIterator> ForwardIterator uninitialized_copy(InputIterator first, InputIterator last, ForwardIterator result);
    シーケンス [first, last) のコピーを [result, RET) に生成し、RETを返します。

  • template<class ForwardIterator, class T> void uninitialized_fill(ForwardIterator first, ForwardIteartor last, const T& x);
    シーケンス [first, last) の領域を xで初期化します。

  • template<class ForwardIterator, class Size, class T> void uninitialized_fill_n(ForwardIterator first, Size n, const T& x);
    first から始まる n 個の領域を x で初期化します。

サンプル・コード

#include <iostream>
#include <vector>
#include <memory>

class X {

  static bool report_;
  int n_;

public:

  X() : n_(-1) { 
    if ( report_ ) { std::cout << "X()\n"; }
  }

  explicit X(int n) : n_(n) {
    if ( report_ ) { std::cout << "X(int:" << n << ":)\n"; }
  }

  X(const X& x) : n_(x.n_) {
    if ( report_ ) { std::cout << "X(const X&:" << x.n_ << ":)\n"; }
  }

  ~X() {
    if ( report_ ) { std::cout << "~X(:" << n_ << ":)\n"; }
  }

  X& operator=(const X& x) {
    n_ = x.n_;
    if ( report_ ) { std::cout << "operator=(const X&:" << x.n_ << ":)\n"; }
    return *this;
  }

  static void enable_report(bool enable) {
    report_ = enable;
  }

  static bool is_enabled_report() {
    return report_;
  }

};

bool X::report_ = false;

// ---------------------------------------

void explicit_destruct(std::pair<X*,ptrdiff_t> buffer_information) {
  bool keep = X::is_enabled_report();
  X::enable_report(false);
  while ( buffer_information.second-- ) {
    buffer_information.first->~X(); // 明示的なデストラクト
    ++buffer_information.first;
  }
  X::enable_report(keep);
}

int main() {

  int i;
  const int N = 5;
  const X x1(1);
  const X x2(2);

  X::enable_report(true);

  // N個のXを格納できる領域を確保する
  std::pair<X*,ptrdiff_t> buffer_information = std::get_temporary_buffer<X>(N);
  std::cout << buffer_information.second << " items allocated at "
            << static_cast(buffer_information.first) << std::endl;

  X* first = buffer_information.first;
  X* last  = first + buffer_information.second;

  X::enable_report(false);
  std::vector<X> xcontainer;
  for (i = 0; i < N; ++i ) {
    xcontainer.push_back(X(i));
  }

  // 通常のiteratorではoperator=によって'代入'される
  X* array = new X[N];
  X::enable_report(true);
  std::cout << "copy using ordinal-iterator" << std::endl;
  std::copy(xcontainer.begin(), xcontainer.end(), array);
  std::cout << std::endl;
  X::enable_report(false);
  delete[] array;
  X::enable_report(true);

  // raw_strage_iteratorではコピー・コンストラクタで'初期化'される
  std::cout << "raw_storage_iterator" << std::endl;
  std::raw_storage_iterator<X*,X> iterator(first);
  std::copy(xcontainer.begin(), xcontainer.end(), iterator);
  std::cout << std::endl;
  explicit_destruct(buffer_information);

  // 初期化されていない領域をコピー・コンストラクタで初期化する
  std::cout << "uninitialized_copy" << std::endl;
  std::uninitialized_copy(xcontainer.begin(), xcontainer.end(), first);
  std::cout << std::endl;
  explicit_destruct(buffer_information);

  // 初期化されていない領域をコピー・コンストラクタで埋める
  std::cout << "uninitialized_fill" << std::endl;
  std::uninitialized_fill(first, last, x1);
  std::cout << std::endl;
  explicit_destruct(buffer_information);

  // 初期化されていない領域をコピー・コンストラクタで埋める
  std::cout << "uninitialized_fill_n" << std::endl;
  std::uninitialized_fill_n(first, N, x2);
  std::cout << std::endl;
  explicit_destruct(buffer_information);

  // 領域を開放する(このときデストラクタは起動されない)
  std::return_temporary_buffer(buffer_information.first);

  X::enable_report(false);
  return 0;
}
実行結果
5 items allocated at 00343E38
copy using ordinal-iterator
operator=(const X&:0:)
operator=(const X&:1:)
operator=(const X&:2:)
operator=(const X&:3:)
operator=(const X&:4:)

raw_storage_iterator
X(const X&:0:)
X(const X&:1:)
X(const X&:2:)
X(const X&:3:)
X(const X&:4:)

uninitialized_copy
X(const X&:0:)
X(const X&:1:)
X(const X&:2:)
X(const X&:3:)
X(const X&:4:)

uninitialized_fill
X(const X&:1:)
X(const X&:1:)
X(const X&:1:)
X(const X&:1:)
X(const X&:1:)

uninitialized_fill_n
X(const X&:2:)
X(const X&:2:)
X(const X&:2:)
X(const X&:2:)
X(const X&:2:)