HOWTO · C++
C++ の配列へのポインタを返す
この記事では、C++ の関数から配列へのポインタを返す方法を示します。
このページの内容
C++ の関数は配列要素へのポインターを返せますが、そのポインター自体には配列の長さも所有権も含まれません。安全に使うには、どのオブジェクトが記憶域を所有しているか、利用できる要素数はいくつか、そのオブジェクトがいつまで有効かを把握する必要があります。通常の結果を返す場合は、生のポインターを返すより、コンテナーを値で返す方が一般に分かりやすくなります。
配列へのポインターが何を指すかを理解する
関数の引数で int values[] と書くと、int* values に調整されます。固定された配列境界は保持されないため、要素数を別に渡すか、サイズを保持するコンテナーを使います。このポインターは最初の要素を指します。values[i] のような添字アクセスで後続の要素を参照できるのは、それらが同じ配列に属し、添字が範囲内にある場合だけです。
values を返すと、そのアドレスが返されます。配列がコピーされたり、有効期間が延びたりするわけではありません。std::array オブジェクト(values など)では、values.data() を呼び出すと要素へのポインターが得られます。このポインターが有効なのは、values オブジェクトが存続し、その記憶域が無効化されていない間だけです。
呼び出し元が所有する配列へのポインターを返す
生のポインターを使う方法の一つは、呼び出し元が配列を所有し、そのアドレスと長さを関数に渡すことです。関数は要素を変更し、呼び出し元が使いやすいように同じポインターを返せます。返されたポインターを使う間は、呼び出し元が配列を存続させ、記憶域が変わらないようにする必要があります。
次の例を実行してください。
このセクションのコードと表示されるラベルや出力は英語のままです。
#include <cstddef>
#include <iostream>
int* subtractFrom(int values[], std::size_t size, int amount) {
for (std::size_t i = 0; i < size; ++i) {
values[i] -= amount;
}
return values;
}
int main() {
int values[] = {5, 10, 15};
int* result = subtractFrom(values, 3, 2);
for (std::size_t i = 0; i < 3; ++i) {
if (i != 0) std::cout << ' ';
std::cout << result[i];
}
std::cout << '\n';
}
3 8 13
配列は main が所有するため、そこで result を読み取る間も有効です。関数の引数から元の配列長は分からないので、サイズは別に渡しています。固定サイズのコンテナーには、サイズを保持する std::array<int, 3> values を使えます。その data() メンバーから、ポインターを必要とするインターフェイスに要素へのポインターを渡せます。
動的に割り当てた配列の記憶域へのポインターを返す
関数は new[] で記憶域を割り当て、そのポインターを返せます。この方法では、呼び出し元が後始末を手動で担います。ポインターを保持し、割り当てた要素数を超えて使わず、使い終えたら delete[] で一度だけ解放します。生のポインターには要素数が記録されないため、呼び出し元はその数も把握しておく必要があります。new[] に単一オブジェクト用の delete を組み合わせる、二重に解放する、または解放し忘れると、エラーやメモリーリークが発生します。
この例では、makeSequence が3つの整数を割り当てて値を設定します。呼び出し元はポインターを受け取り、既知の要素数に従って使った後、対応する delete[] で配列を解放します。
このセクションのコードと表示されるラベルや出力は英語のままです。
#include <cstddef>
#include <iostream>
int* makeSequence(std::size_t count) {
int* values = new int[count];
for (std::size_t i = 0; i < count; ++i) {
values[i] = static_cast<int>(i * 5 + 3);
}
return values;
}
int main() {
const std::size_t count = 3;
int* values = makeSequence(count);
for (std::size_t i = 0; i < count; ++i) {
std::cout << values[i] << '\n';
}
delete[] values;
}
この方法なら実行時に決まるサイズを扱えますが、所有権を見失いやすくなります。可変長の結果には、std::vector<int> を値で返す方法を優先してください。vector が割り当てを管理し、サイズを保持するとともに、効率よくムーブできます。呼び出し元が要素を使えるのは、vector が存続し、再割り当てを伴う操作によって記憶域が無効化されていない間だけです。
ダングリングポインターを避け、安全性の高い戻り値の型を選ぶ
自動記憶域期間を持つローカル配列へのポインターを返してはいけません。関数が戻るとその有効期間が終わり、呼び出し元にはダングリングポインターが残ります。関数ローカルの static 配列はこれと異なります。静的記憶域期間はプログラムの終了まで続くため、関数が戻っただけではポインターは無効になりません。ただし、呼び出しのたびに同じ可変配列を共有し、容量も固定されているため、並行または入れ子になった利用が互いに干渉することがあります。共有状態が意図的な場合に限り、静的記憶域を使ってください。
固定サイズの結果には std::array<int, N> を値で返します。可変サイズの結果には std::vector<int> を値で返します。これらの型は所有権を明確にし、呼び出し元が生のポインターと別個の解放規則を組み合わせる必要をなくします。API が要素へのポインターを返さなければならない場合は、所有するオブジェクトとポインターが有効な期間を説明し、所有者が破棄された後や記憶域が変化した後にそのポインターを使わないでください。