HOWTO · C++

C++ でベクトルを配列に変換する方法

この記事では、C++ でベクトルを配列に変換する方法を紹介します。

このページの内容

std::vector の要素はメモリ上に連続して格納されるため、ポインターと要素数を受け取る API では、その保存領域を直接利用できます。配列のようにアクセスできますが、独立した配列が作られるわけではありません。ポインターが指すのは、ベクターが所有する要素です。呼び出し先で独立した保存領域が必要なら、全要素を格納できるサイズの保存先へコピーしてください。

一時的にポインターで参照するには data()、独立したコピーを作るには std::copy、コピー時に値を変更するには std::transform を使います。要素へのポインターを使っている間は、ベクターを生存させてください。再割り当てが起きると、その保存領域を指すポインターは使えなくなります。

data() でベクターの要素に配列のようにアクセスする

data() メンバー関数は、ベクターの連続した要素保存領域を指すポインターを返します。ポインターと要素数を受け取る API にデータを渡すときは、size() と組み合わせてください。このポインターはベクターの要素を参照します。ポインター経由で書き込むと対応する要素も変わり、ベクターや、その保存領域を再割り当てする操作より長く使うことはできません。

次の例を実行してください。

このセクションのコードと表示されるラベルや出力は英語のままです。

#include <cstddef>
#include <iostream>
#include <vector>

int main() {
  std::vector<int> values{1, 2, 3};
  int* elements = values.data();
  elements[1] = 20;

  for (std::size_t i = 0; i < values.size(); ++i) {
    if (i != 0) std::cout << ' ';
    std::cout << elements[i];
  }
  std::cout << '\n';
}
1 20 3

空の場合もあるベクターから data() を取得できますが、要素が存在しない限り、返されたポインターを間接参照してはいけません。ポインターで要素を参照するときは、必ずベクターのサイズを上限として使ってください。

空でないベクターでアドレス取得演算子を使う

少なくとも1つの要素があると分かっているベクターでは、&values[0] が先頭要素を指し、そのポインターからベクター内の要素を順に参照できます。このポインターの所有元と有効期間は、data() で取得したポインターと同じです。前提条件に注意してください。空のベクターで要素0を参照するのは無効です。空の場合もあるベクターでは data() を使い、間接参照する前や、少なくとも1つの要素を必要とするコードにポインターを渡す前に、サイズを確認してください。

このセクションのコードと表示されるラベルや出力は英語のままです。

#include <cstddef>
#include <iostream>
#include <vector>

int main() {
  std::vector<int> values{4, 5, 6};
  int* elements = &values[0];

  for (std::size_t i = 0; i < values.size(); ++i) {
    if (i != 0) std::cout << ' ';
    std::cout << elements[i];
  }
  std::cout << '\n';
}
4 5 6

std::copy() でベクターの要素を配列にコピーする

std::copy は、あらかじめ作成された保存先の範囲に各要素を書き込みます。その範囲を確保する機能はありません。要素数がコンパイル時に分かる場合、std::array は実体のある固定サイズ配列オブジェクトとして使えます。サイズと要素型は、コピー元の要素をすべて格納できるものでなければなりません。要素数が実行時にしか分からない場合は、保存先に std::vector などの適切な動的ストレージを使うか、受け取り側の API が明示的に必要とする場合は所有権を持つ動的配列を使ってください。

このセクションのコードと表示されるラベルや出力は英語のままです。

#include <algorithm>
#include <array>
#include <cstddef>
#include <iostream>
#include <vector>

int main() {
  std::vector<int> values{7, 8, 9};
  std::array<int, 3> result{};
  std::copy(values.begin(), values.end(), result.begin());

  for (std::size_t i = 0; i < result.size(); ++i) {
    if (i != 0) std::cout << ' ';
    std::cout << result[i];
  }
  std::cout << '\n';
}
7 8 9

保存先はコピー元から独立しているため、result を変更しても values は変わりません。std::copy を呼び出す前に、保存先にコピー元の全要素を格納できる領域があることを確認してください。このアルゴリズムは保存先の容量を確認しません。可変長の組み込み配列は標準 C++ ではありません。配列の境界はコンパイル時定数でなければならないためです。

std::transform() でコピーしながらベクターの要素を変換する

コピーと同時に新しい値を計算する場合は std::transform を使います。出力範囲には、入力要素ごとに1つの結果を格納できる領域が必要です。これは std::copy と同様です。この例では、各整数を2倍にして、独立した std::array に格納します。

このセクションのコードと表示されるラベルや出力は英語のままです。

#include <algorithm>
#include <array>
#include <cstddef>
#include <iostream>
#include <vector>

int main() {
  std::vector<int> values{2, 3, 4};
  std::array<int, 3> doubled{};
  std::transform(values.begin(), values.end(), doubled.begin(),
                 [](int value) { return value * 2; });

  for (std::size_t i = 0; i < doubled.size(); ++i) {
    if (i != 0) std::cout << ' ';
    std::cout << doubled[i];
  }
  std::cout << '\n';
}
4 6 8

値を変えずにコピーするなら、std::copy のほうが意図を直接表せます。変換後の結果を格納できる型を保存先に選び、入力範囲全体を収められるサイズにしてください。

for ループでベクターの要素をコピーする

ループを使うと、各要素の代入を明示的に制御できます。保存先のサイズに関する要件は同じです。コピー元で使うすべてのインデックスが、保存先でも有効でなければなりません。コンパイル時にサイズが決まる保存先を使う場合は、コピー前にその大きさがベクターと一致することを確認してください。実行時にサイズが決まる入力には、十分な大きさの動的な保存先を使います。

このセクションのコードと表示されるラベルや出力は英語のままです。

#include <array>
#include <cstddef>
#include <iostream>
#include <vector>

int main() {
  std::vector<int> values{10, 11, 12};
  std::array<int, 3> result{};

  for (std::size_t i = 0; i < values.size(); ++i) {
    result[i] = values[i];
  }

  for (std::size_t i = 0; i < result.size(); ++i) {
    if (i != 0) std::cout << ' ';
    std::cout << result[i];
  }
  std::cout << '\n';
}
10 11 12

API がベクター所有の要素を一時的に参照する必要がある場合は data() を選び、その間はベクターを生存させ、保存領域を再割り当てしないでください。独立した保存領域が必要なら std::copy、コピー時に値を変更するなら std::transform、要素ごとの制御が必要ならループを使います。要素数がコンパイル時に分かる場合は固定サイズの std::array が適しています。それ以外では、API が許すなら実行時にサイズを決められる保存先を使ってください。