🍣

【C++】vector自身をstd::back_inserterできない理由

2023/03/09に公開

トピック

既存の動的配列があったとして、その配列自身を最後尾に付け加えて、ちょうどもとの繰り返しになるような配列を作る場合を考える。そこで、以下のようなコードを実行する。

int main() {

	std::vector<int> vec{ 1, 2, 3 ,4, 5 };
	std::copy(std::begin(vec), std::end(vec), std::back_inserter(vec));
	for (auto v : vec) std::cout << v << ' ';

	return 0;
}

さて、これで期待通りに動作するだろうか。すなわち

1 2 3 4 5 1 2 3 4 5 

なる出力結果が得られるだろうか。答えはNoである。私の環境下では

1 2 3 4 5 1 -572662307 -572662307 -572662307 -572662307

となった。原因は一体何だろうか。

原因

結論から言うと、std::back_inserterの仕様がキモである。std::back_inserterは、要するに引数となる動的配列に対してstd::push_backを繰り返し行っている。std::push_back(vec)が行っているのは、以下の内容だ。

  • 引数となる配列よりもサイズが1大きい新しい配列new_vecを生成する。
  • 元の配列vecの各要素をnew_vecに同じ位置にコピーする
  • 末尾に追加する要素をnew_vecの最後のコンテナに代入する。
  • vecを破壊する。
  • new_vecvecとする。

もう気づいた方もいるのではないだろうか。

std::copy(std::begin(vec), std::end(vec), std::back_inserter(vec));

で参照していくイテレータは下図のようになる。

これがstd::back_inserterによってどうなるかというと、先ほどの説明により以下のような状態になる。

イテレータの指す場所にある要素は、もう既に破壊されているためundefinedな操作となる。
したがって、最初の要素を除いて正しい値が挿入されないといった現象が発生する。

解決策

シンプルに別の動的配列を定義すればよい。

int main() {

	std::vector<int> vec{ 1, 2, 3 ,4, 5 };
	std::vector<int> vec1(5);
	std::copy(std::begin(vec), std::end(vec), std::begin(vec1));
	std::copy(std::begin(vec1), std::end(vec1), std::back_inserter(vec));
	for (auto v : vec) std::cout << v << ' ';

	return 0;
}

Discussion