C++で配列をコピーする場合、まず理解しておきたいのは、生配列は代入だけではコピーできないという点です。
たとえば、ある配列の内容を別の配列にそのまま移したい場合でも、生配列同士を単純に代入することはできません。
これはC++の配列が、通常の変数や標準ライブラリのコンテナとは異なる性質を持っているためです。
そのため、C++で配列をコピーするには、目的や配列の種類に応じて適切な方法を選ぶ必要があります。
配列コピーの方法には、主に次のような考え方があります。
- 要素を1つずつコピーする
- 標準ライブラリのコピー機能を使う
- メモリ単位でコピーする
std::arrayやstd::vectorのような標準コンテナを使う
特に現代的なC++では、生配列を直接扱うよりも、std::array や std::vector を使う方が安全で分かりやすい場面が多いです。
生配列は代入でコピーできない
生配列のコピーで最も重要なポイント
C++の生配列は、通常の変数のように代入で中身をコピーすることができません。
整数や文字列などの変数であれば、片方の値をもう片方に代入できます。
しかし、生配列の場合は、配列全体をまとめて代入することができない仕様になっています。
そのため、生配列の内容を別の配列にコピーしたい場合は、別の方法を使う必要があります。
初期化時も別の配列から直接コピーできない
生配列では、代入だけでなく、別の配列を使ってそのまま初期化することも基本的にはできません。
配列を作るときに値を直接並べて初期化することはできますが、すでに存在している別の配列をそのまま渡して、新しい配列を作るような書き方はできません。
つまり、生配列では「別の配列を丸ごとコピーして新しい配列を作る」という操作を、単純な代入や初期化だけで行うことはできない、という理解が重要です。
for文で要素を1つずつコピーする方法
最も基本的で分かりやすい方法
配列コピーの基本は、要素を1つずつ取り出して別の配列に入れる方法です。
これは、コピー元の配列の先頭から最後まで順番に見ていき、それぞれの要素をコピー先の同じ位置に代入していく考え方です。
たとえば、5個の要素を持つ配列であれば、1番目の要素をコピー先の1番目へ、2番目の要素をコピー先の2番目へ、というように処理します。
学習段階ではfor文が理解しやすい
C++を学び始めた段階では、for文によるコピーが最も理解しやすいです。
なぜなら、配列のどの要素をどこにコピーしているのかが明確だからです。
標準ライブラリの機能を使う前に、まずは要素単位でコピーする考え方を理解しておくと、後で std::copy などを学ぶときにも理解しやすくなります。
ただし、実務では、手書きのfor文よりも標準ライブラリを使った方が読みやすく、安全に書ける場合があります。
std::copyを使う方法
C++らしい配列コピーの方法
C++では、標準ライブラリに用意されている std::copy を使って配列をコピーできます。
std::copy は、指定した範囲の要素を、コピー先に順番にコピーするための機能です。
生配列だけでなく、std::array や std::vector などにも使えるため、C++ではよく使われます。
std::copyの考え方
std::copy では、コピー元の開始位置、コピー元の終了位置、コピー先の開始位置を指定します。
このとき、C++では「開始位置は含むが、終了位置は含まない」という範囲指定がよく使われます。
これを半開区間と呼びます。
たとえば、5個の要素をコピーする場合は、1番目から5番目までの要素を対象にしますが、範囲指定としては「先頭から、5個分進んだ位置の直前まで」という形になります。
コピー先のサイズに注意する
std::copy を使うときに特に注意したいのは、コピー先の配列サイズです。
std::copy は、コピー先に十分な領域があるかを自動では確認してくれません。
そのため、コピー元に5個の要素があるのに、コピー先に3個分しか領域がない場合、範囲外に書き込んでしまう危険があります。
このような処理は未定義動作につながるため、コピー元とコピー先のサイズには必ず注意する必要があります。
std::copy_nを使う方法
コピーする個数が明確な場合に便利
std::copy_n は、コピーする個数を指定して配列をコピーする方法です。
std::copy が「開始位置から終了位置まで」という範囲を指定するのに対して、std::copy_n は「先頭から何個コピーするか」を指定します。
そのため、コピーする要素数がはっきりしている場合は、std::copy_n の方が意図を読み取りやすいことがあります。
std::copyとの違い
std::copy は範囲を指定する方法です。
一方で、std::copy_n は個数を指定する方法です。
どちらも配列コピーに使えますが、コードを読む人にとって分かりやすい方を選ぶとよいです。
たとえば、「この配列から5個だけコピーする」という意図を明確にしたい場合は、std::copy_n が向いています。
memcpyを使う方法
メモリをそのままコピーする方法
memcpy は、配列の内容をメモリ単位でコピーする方法です。
C言語由来の機能で、指定したバイト数分だけ、コピー元からコピー先へメモリの内容をそのまま複製します。
整数や文字など、単純なデータ型の配列であれば、memcpy を使ってコピーできる場合があります。
memcpyが使える場面
memcpy が向いているのは、内部に複雑な管理情報を持たない型です。
たとえば、整数型、浮動小数点型、文字型などの配列では、memcpy を使っても問題になりにくいです。
より正確に言うと、C++では memcpy は trivially copyable な型に対して使うべきです。
これは、単純にメモリ内容をコピーしてもオブジェクトとして問題が起きにくい型のことです。
memcpyを使うべきではない場面
std::string や std::vector、独自のクラスなどには、基本的に memcpy を使うべきではありません。
これらの型は、内部でメモリ管理やリソース管理を行っていることがあります。
memcpy は、コピーコンストラクタや代入演算子を呼ばず、ただメモリの内容だけをコピーします。
そのため、オブジェクトの内部状態が壊れたり、二重解放などの問題が起きたりする可能性があります。
C++のオブジェクトを安全にコピーしたい場合は、memcpy ではなく、std::copy や標準コンテナのコピー機能を使う方が適切です。
std::arrayを使ったコピー
固定長配列ならstd::arrayが便利
固定長の配列を扱う場合は、生配列ではなく std::array を使う方法があります。
std::array は、C++標準ライブラリに用意されている固定長配列のためのコンテナです。
生配列と似たように固定個数の要素を持ちますが、より安全で扱いやすい特徴があります。
std::arrayは代入でコピーできる
生配列とは違い、std::array は代入によって内容をコピーできます。
つまり、同じ型・同じ要素数の std::array 同士であれば、片方の内容をもう片方にそのままコピーできます。
これは生配列との大きな違いです。
std::arrayのメリット
std::array には、配列サイズを取得しやすい、標準ライブラリの機能と組み合わせやすい、関数に渡したときに扱いやすいといったメリットがあります。
また、固定長であることが型に含まれているため、要素数の違う std::array 同士は別の型として扱われます。
たとえば、3個の要素を持つ std::array と、4個の要素を持つ std::array は別の型です。
そのため、サイズの違う配列を誤って代入することはできません。
std::vectorを使ったコピー
可変長配列ならstd::vectorが適している
要素数が実行時に変わる場合は、std::vector を使うのが一般的です。
std::vector は、C++で最もよく使われる動的配列のようなコンテナです。
要素数を増減でき、メモリ管理も自動で行ってくれます。
std::vectorも代入でコピーできる
std::vector も、代入によってコピーできます。
コピー元の std::vector をコピー先に代入すると、コピー先の中身はコピー元と同じになります。
コピー先にもともと別の要素が入っていた場合、それらは置き換えられます。
実務ではstd::vectorがよく使われる
現代的なC++では、可変長の配列を扱う場合、直接 new や delete を使って動的配列を管理するよりも、std::vector を使う方が安全です。
std::vector を使えば、メモリ解放忘れやサイズ管理のミスを減らすことができます。
配列の一部だけをコピーする場合
必要な範囲だけコピーできる
配列全体ではなく、一部だけをコピーしたい場合もあります。
その場合は、コピー元の開始位置と終了位置を調整することで、必要な範囲だけをコピーできます。
たとえば、配列の2番目から4番目までだけを別の配列にコピーする、といった処理が可能です。
終了位置は含まれない点に注意
C++の範囲指定では、終了位置は含まれません。
そのため、「どこまでコピーしたいか」を考えるときは、終了位置の扱いに注意が必要です。
コピーしたい最後の要素そのものではなく、その次の位置を終了位置として考えます。
この考え方は、配列コピーだけでなく、C++の標準ライブラリ全体でよく使われます。
2次元配列のコピー
for文でコピーする方法
2次元配列をコピーする場合も、基本的な考え方は1次元配列と同じです。
行と列を順番にたどりながら、各要素をコピー先に代入していきます。
この方法は分かりやすく、2次元配列の構造を理解するうえでも有効です。
連続したメモリとしてコピーする方法
通常の固定長2次元配列は、メモリ上で連続して配置されています。
そのため、全体を1つの連続した領域として扱い、まとめてコピーすることもできます。
ただし、この考え方が使えるのは、通常の固定長2次元配列の場合です。
int型ポインタのポインタとは違う
注意したいのは、2次元配列と、ポインタを使って作った「2次元配列のようなもの」は別物だという点です。
たとえば、ポインタのポインタで表現された配列は、各行がメモリ上で連続しているとは限りません。
そのため、固定長2次元配列と同じように、全体を1つの連続領域としてコピーするのは危険です。
関数に配列を渡すときの注意点
生配列は多くの場合ポインタとして扱われる
C++では、生配列を関数に渡すと、多くの場合、配列そのものではなく先頭要素へのポインタとして扱われます。
そのため、関数側では配列の要素数が分からなくなります。
配列を関数に渡してコピー処理を行う場合は、配列のサイズも一緒に渡す必要があります。
sizeofの使い方に注意する
配列そのものに対して sizeof を使うと、配列全体のバイト数を取得できます。
しかし、関数に渡された後の配列に対して sizeof を使うと、多くの場合はポインタのサイズが返ります。
これは初心者がよく間違えるポイントです。
関数の中で配列のサイズを知りたい場合は、サイズを別の引数として渡すか、std::array や std::vector を使う方が安全です。
配列参照を使う方法もある
C++では、配列を参照として受け取ることもできます。
配列参照を使えば、配列のサイズを型情報として保持できます。
さらにテンプレートを使うことで、配列サイズを自動的に扱うこともできます。
ただし、これはやや中級者向けの書き方です。
入門段階では、まず「生配列を関数に渡すとサイズ情報が失われやすい」という点を押さえておくとよいです。
文字配列のコピー
C文字列では終端文字に注意する
文字配列を使って文字列を扱う場合は、最後にヌル文字が必要です。
たとえば、見た目には5文字の文字列であっても、内部的には文字列の終わりを示すためのヌル文字が加わります。
そのため、コピー先の文字配列には、文字数よりも1文字分多い領域が必要です。
strcpyはサイズチェックをしない
C文字列のコピーには strcpy が使われることがあります。
ただし、strcpy はコピー先のサイズを確認しません。
コピー元の文字列がコピー先より長い場合、バッファオーバーフローが起きる可能性があります。
そのため、C++では文字列を扱う場合、基本的には std::string を使う方が安全です。
std::stringならコピーが簡単
std::string を使えば、文字列のコピーは簡単に行えます。
代入によって文字列の内容をコピーでき、メモリ管理も自動で行われます。
C++で文字列を扱うなら、特別な理由がない限り、文字配列よりも std::string を使う方がよいです。
浅いコピーと深いコピー
値の配列では値そのものがコピーされる
整数や浮動小数点数などの値を持つ配列では、コピーすると各要素の値がコピーされます。
コピー後にコピー先の要素を変更しても、コピー元の要素には影響しません。
このようなコピーは、直感的に理解しやすいです。
ポインタ配列ではアドレスがコピーされる
一方で、ポインタを要素に持つ配列の場合、コピーされるのはポインタの値、つまりアドレスです。
そのため、コピー元とコピー先のポインタが同じデータを指すことになります。
この状態で、コピー先のポインタを通じてデータを書き換えると、コピー元側から見ても同じデータが変更されたように見えます。
ポインタ先のデータまではコピーされない
ポインタ配列をコピーしても、ポインタが指している先のデータまでは自動的に複製されません。
このように、参照先やポインタ先を共有するコピーを浅いコピーと呼びます。
一方で、指している先のデータまで別々に複製するコピーを深いコピーと呼びます。
ポインタを含む配列を扱う場合は、単に配列をコピーするだけで十分なのか、ポインタ先のデータまでコピーする必要があるのかを考える必要があります。
動的配列のコピー
newで確保した配列もコピーは可能
new を使って動的に確保した配列も、要素を順番にコピーすることはできます。
この場合も、生配列と同じように、コピー元の先頭から必要な個数分だけコピー先へ移します。
ただし、コピー先にも十分な領域を確保しておく必要があります。
メモリ解放忘れに注意する
動的配列を使う場合は、確保したメモリを適切に解放しなければなりません。
メモリ解放を忘れると、メモリリークにつながります。
また、誤ったタイミングで解放すると、プログラムの不具合につながる可能性があります。
そのため、現代的なC++では、直接 new や delete を使うよりも、std::vector を使う方が安全です。
コピー先のサイズ不足に注意する
配列コピーで最も危険なミス
配列コピーで特に注意すべきなのは、コピー先のサイズ不足です。
コピー元に多くの要素があるのに、コピー先の配列が小さい場合、コピー処理によって配列の範囲外に書き込みが発生する可能性があります。
このような処理は未定義動作につながります。
C++では自動で安全確認されない場合がある
生配列や std::copy を使う場合、コピー先のサイズが十分かどうかは、基本的にプログラマーが管理する必要があります。
そのため、配列サイズを直接書く場合は、コピー元とコピー先の要素数が一致しているかを慎重に確認する必要があります。
サイズ管理のミスを減らしたい場合は、std::array や std::vector を使う方が安全です。
重なった範囲をコピーする場合の注意点
同じ配列内でコピーする場合は注意が必要
配列のコピーでは、コピー元とコピー先が別の配列であれば問題になりにくいですが、同じ配列内で要素をずらすような処理では注意が必要です。
コピー元の範囲とコピー先の範囲が重なっている場合、単純なコピーでは期待通りの結果にならないことがあります。
後ろ方向へのコピーが必要な場合がある
同じ配列内で、要素を右側にずらすような処理では、前から順番にコピーすると、まだコピーしていない値を上書きしてしまう可能性があります。
そのような場合は、後ろから順番にコピーする方法を使う必要があります。
C++には、このような場面に対応するための機能も用意されています。
std::beginとstd::endを使うと安全性が上がる
配列サイズを直接書かずに済む
生配列をコピーするとき、要素数を直接書くと、後から配列サイズを変更した際に修正漏れが起きやすくなります。
そのようなミスを減らすために、std::begin と std::end を使う方法があります。
これらを使うと、配列の開始位置と終了位置を自動的に取得できます。
実務では読みやすさにもつながる
std::begin と std::end を使うと、配列全体をコピーしていることが分かりやすくなります。
特に、配列の要素数をコード中に直接書かなくてよいため、保守性が上がります。
C++20以降ならstd::ranges::copyも選択肢になる
より現代的なコピー方法
C++20以降では、std::ranges::copy という機能も使えます。
これは、範囲をより自然に扱える仕組みで、配列全体やコンテナ全体をコピーする処理を読みやすく書けます。
古い環境では使えない場合がある
ただし、std::ranges::copy はC++20以降の機能です。
そのため、古いコンパイラや古い開発環境では使えない場合があります。
学習段階では、まず std::copy を理解し、その後でC++20以降の機能として std::ranges::copy を学ぶとよいです。
コピー方法の使い分け
生配列を学習している場合
C++の基礎を学んでいる段階では、まずfor文で要素を1つずつコピーする方法を理解するとよいです。
この方法を理解すると、配列の仕組みやインデックスの扱いが身につきます。
生配列を簡潔にコピーしたい場合
生配列を使う必要があり、簡潔にコピーしたい場合は、std::copy や std::copy_n を使うのが適しています。
特に、C++らしい書き方をするなら、手書きのfor文よりも標準ライブラリを活用する方が望ましいです。
固定長配列を扱う場合
要素数が固定で、配列として扱いたい場合は、std::array が適しています。
std::array は生配列より扱いやすく、代入コピーも可能です。
サイズ情報も保持しやすいため、安全性が高くなります。
可変長配列を扱う場合
要素数が実行時に変わる場合は、std::vector が適しています。
std::vector はメモリ管理を自動で行ってくれるため、動的配列を直接扱うよりも安全です。
単純な型を高速にコピーしたい場合
整数や文字など、単純な型の配列であれば、memcpy を使える場合もあります。
ただし、C++のクラス型や内部でリソース管理を行う型には使わない方がよいです。
安全性を重視するなら、std::copy を使うのが無難です。
実務ではどの方法を選ぶべきか
基本はstd::arrayかstd::vectorを優先する
現代的なC++では、生配列を直接使うよりも、固定長なら std::array、可変長なら std::vector を使うのがおすすめです。
これらはコピーが簡単で、標準ライブラリとの相性もよく、サイズ管理もしやすいです。
生配列が必要な場合はstd::copyを使う
既存のコードやC言語との連携などで生配列を使う必要がある場合は、std::copy を使うとよいです。
手書きのfor文でもコピーできますが、標準ライブラリを使うことで意図が明確になり、コードの見通しもよくなります。
memcpyは限定的に使う
memcpy は便利ですが、使いどころを間違えると危険です。
単純な型の配列を高速にコピーしたい場合などに限定して使い、std::string や std::vector、独自クラスなどには使わないようにしましょう。
C++の配列コピーで押さえるべきポイント
生配列は代入コピーできない
C++の生配列は、代入によって配列全体をコピーすることはできません。
そのため、for文や標準ライブラリのコピー機能を使う必要があります。
コピー先のサイズを必ず確認する
配列コピーでは、コピー先に十分な領域があるかを必ず確認する必要があります。
サイズ不足のままコピーすると、範囲外アクセスや未定義動作につながります。
memcpyは使える型を選ぶ
memcpy はメモリをそのままコピーするため、単純な型には使えますが、複雑なオブジェクトには向いていません。
C++では、型に応じて安全なコピー方法を選ぶことが重要です。
文字列はstd::stringを使うと安全
文字配列を直接扱う場合は、終端文字やコピー先サイズに注意が必要です。
C++で文字列を扱うなら、基本的には std::string を使う方が安全で分かりやすいです。
固定長ならstd::array、可変長ならstd::vector
固定長の配列には std::array、可変長の配列には std::vector を使うと、コピー処理が簡単になります。
現代的なC++では、この2つを優先して使うのが実務的です。
まとめ
C++の配列コピーでは、まず生配列は代入でコピーできないという点を理解することが重要です。
生配列をコピーする場合は、for文で1つずつコピーする方法や、std::copy、std::copy_n などの標準ライブラリを使う方法があります。
一方で、現代的なC++では、生配列よりも std::array や std::vector を使う方が安全で扱いやすいです。
固定長なら std::array、可変長なら std::vector を使うことで、コピーも代入だけで簡単に行えます。
また、memcpy は単純な型には使える場合がありますが、std::string や std::vector などの複雑な型には使うべきではありません。
配列コピーでは、コピー方法だけでなく、コピー先のサイズ、型の性質、浅いコピーと深いコピーの違いも理解しておく必要があります。
実務的には、次の順番で考えるとよいです。
- 可変長なら
std::vectorを使う - 固定長なら
std::arrayを使う - 生配列が必要な場合は
std::copyを使う memcpyは条件を理解したうえで限定的に使う
この考え方を押さえておけば、C++の配列コピーで大きなミスを避けやすくなります。
以上、C++の配列のコピーについてでした。
最後までお読みいただき、ありがとうございました。
