HOWTO · C++
C++ でのベクトルコンテナオブジェクトのコピー
この記事では、C++ でベクターコンテナオブジェクトをコピーする方法を示します。
このページの内容
C++ の std::vector をコピーするには、新しい vector を作るときはコピーコンストラクターを使い、既存の vector の内容を置き換えるときはコピー代入を使います。範囲を選んでコピーする場合は、範囲コンストラクターを使います。std::copy は宛先範囲に十分な要素数がある場合に使え、std::back_inserter は宛先の末尾に要素を追加します。これらの操作は要素型に応じて要素をコピーしますが、ポインターが指すオブジェクトまで再帰的にコピーするわけではありません。
コピーコンストラクターまたは代入演算子で vector をコピーする
コピー初期化では、既存の vector から新しい vector を作ります。std::vector<int> copy = source; のように書きます。直接初期化も同じ処理を行い、丸かっこを使います。std::vector<int> copy(source);。宛先がすでに存在する場合は、代入を使います。destination = source;。代入すると、宛先の要素は置き換えられます。これらの形式を使うには <vector> をインクルードします。
次の例を実行します。
このセクションのコードと表示されるラベルや出力は英語のままです。
#include <cstddef>
#include <iostream>
#include <vector>
int main() {
const std::vector<int> source{2, 4, 6};
std::vector<int> copy = source;
std::vector<int> destination{9};
destination = source;
for (std::size_t i = 0; i < copy.size(); ++i) {
if (i != 0) {
std::cout << ' ';
}
std::cout << copy[i];
}
std::cout << '\n';
for (std::size_t i = 0; i < destination.size(); ++i) {
if (i != 0) {
std::cout << ' ';
}
std::cout << destination[i];
}
std::cout << '\n';
}
2 4 6
2 4 6
このプログラムは、コピーした各 vector をそれぞれ別の行に出力します。std::vector<int> の要素は値なので、一方の vector の整数を変更しても、もう一方の対応する整数は変わりません。
vector の範囲コンストラクターで範囲をコピーする
範囲コンストラクターには半開区間のイテレーター範囲を渡します。最初のイテレーターが示す要素は含まれ、2つ目のイテレーターが示す要素は含まれません。初期化リスト構文と区別するため、丸かっこを使います。
std::vector<int> copy(source.begin(), source.end()); は範囲全体をコピーします。一部の範囲をコピーするには、そのイテレーターが目的の要素を示していることを確認したうえで、std::vector<int> middle(source.begin() + 1, source.end() - 1); のようにします。
最初の宣言は全要素をコピーし、2つ目の宣言はこの例では内側の範囲をコピーします。両方のイテレーターは、同じ元の vector 内の有効な範囲を示している必要があります。新しい宛先を作る場合、vector 全体にも部分範囲にも便利な方法です。
std::copy で範囲をコピーする
std::copy は既存の宛先範囲に要素を書き込みます。<algorithm> をインクルードし、コピーする前に、宛先の要素数がコピー元の範囲と同じかそれ以上であることを確認してください。このアルゴリズムだけでは vector のサイズは増えません。
std::vector<int> destination(source.size()); を用意してから std::copy(source.begin(), source.end(), destination.begin()); を呼び出すと、コピー元に足りる大きさの宛先範囲を確保できます。
一部の範囲をコピーする場合は、その範囲の開始イテレーターと終了イテレーターを渡し、宛先にその範囲を受け入れるだけの要素を用意します。コピー元と宛先の範囲が、アルゴリズムの要件に反する形で重ならないようにしてください。
std::back_inserter でコピーした要素を追加する
要素を追加し、必要に応じて vector を拡張するには、<algorithm> と <iterator> をインクルードし、std::back_inserter を使います。
たとえば、std::vector<int> destination{10}; を用意してから std::copy(source.begin(), source.end(), std::back_inserter(destination)); を呼び出すと、コピー元の要素が追加されます。
先頭には元からあった 10 が残り、その後ろにコピー元の値が続きます。宛先にすでに要素がある場合や、最終的な要素数をあらかじめ決めない場合に便利です。
ループと push_back で要素をコピーする
ループを使うと、要素ごとの処理を明示できます。push_back はコピーした値を末尾に追加するため、既存の宛先はその分だけ拡張されます。
std::vector<int> destination; と宣言した後、次のようにループを書けます。for (const int& value : source) { destination.push_back(value); }
通常のコピーでは、vector のコピーコンストラクターや範囲コンストラクターのほうが簡潔です。要素を追加するときに変換やフィルター処理も行う場合は、ループが適しています。ループでも標準の vector 操作と同様に、各要素はその型に応じてコピーされます。
assign と insert で宛先の要素を置き換える、または挿入する
宛先を範囲の内容で置き換える場合は assign(first, last) を使います。宛先のほかの要素を残したまま、コピー元の範囲を指定位置に追加する場合は insert(position, first, last) を使います。
内容を置き換えるには destination.assign(source.begin(), source.end()); とします。コピー元の範囲を先頭に挿入するには destination.insert(destination.begin(), source.begin(), source.end()); とします。
挿入位置には、宛先内の有効なイテレーターを指定する必要があります。挿入によって vector の記憶領域が再確保されると、要素を指すすべてのイテレーター、ポインター、参照が無効になります。再確保が起きない場合でも、挿入位置以降のイテレーターと参照は無効になりますが、それより前のものは有効なままです。挿入後は、影響を受けたイテレーターや参照を取り直してください。
vector のコピーで複製されるものを理解する
std::vector は要素を連続した領域に格納し、その領域を管理します。vector をコピーすると、要素のコピーを格納した別の vector が作られます。std::vector<int> の場合は整数値がコピーされるため、一方の要素に後から代入しても、もう一方の値は変わりません。
ほかの型の要素については、その型のコピー操作に従います。たとえば、生ポインターを格納した vector をコピーすると、ポインターの値がコピーされますが、ポインターの指すオブジェクトは新たに作られません。要素がスマートポインターなら、std::shared_ptr の共有所有権など、その型自身のコピー規則に従います。そのため、vector が別々でも、要素の先にあるオブジェクトまで独立しているとは限りません。
vector のコピー方法を選ぶ
新しい vector を作るには、コピーコンストラクターまたは範囲コンストラクターを選びます。既存の宛先を置き換えるには、コピー代入または assign を使います。範囲を指定位置に追加するには insert を使い、宛先を拡張しながら末尾に追加するには std::copy と std::back_inserter を組み合わせます。確保済みの要素範囲に書き込みたい場合は、サイズを設定した宛先に std::copy を使います。コピーと同時に要素ごとの独自処理を行う場合は、ループと push_back が便利です。
vector 全体を新しくコピーするなら、コピーコンストラクターを使います。選択した要素だけをコピーするには範囲コンストラクターを使います。既存の vector では、内容の置き換えに代入または assign、指定位置への追加に insert、末尾への追加に std::back_inserter を選びます。