C/C++初心者ですが、仕事でC++の中規模開発に携わることになりました。今までJavaで開発していたのですが、Javaでの開発は要素数が事前にわかっていれば極力配列を使えと教えられてきましたが、C++では配列を扱うのはstd::vectorがメインのように感じます。処理が簡潔になるのはよくわかるのですが、配列を使ってもっとレガシーな感じで書きたいのですが実際の現場ではどう使われているのか、皆様のご意見をお聞かせ頂きたく投稿させていただきました。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2018/05/18 13:55
回答8件
0
c++では配列[]はあまり使わないものなんですか?
(私個人の希望的予測も含めて)はい。
配列要素数がある程度少なく、恒久的に要素数が変化しないと確信できるケースを除いて、生の配列型よりも std::vector<T>
や std::array<T,N>
クラスの利用を推奨します。
Java言語とC++言語の大きな違いとして、Javaの配列型はそれ自身が要素数(length
)を管理しているが、C++の配列型の要素数はプログラマの責任で別途管理する必要があります。C++配列型の利用はプログラマの責任範囲が大きく、典型的なバグの温床になっています。
Java
1void method(int[] arr) { 2 int len = arr.length; // 配列arr自身が要素数を知っている 3}
C++
1void method(int arr[], size_t n) { 2 // 配列arrとは別に要素数nをセットで管理する必要がある 3 // 実際の要素数とあわせて管理しないと致命的なバグを引き起こす 4} 5 6void method(std::vector<int>& v) { 7 size_t n = n.size(); // vector型自身は要素数を知っている 8} 9 10void method(std::array<int, 10>& v) { 11 size_t n = n.size(); // array型自身は要素数を知っている(n==10) 12}
投稿2018/05/18 13:42
編集2018/05/18 13:45総合スコア6191
0
各言語の配列またはリストを集めました。レガシーな実装、抽象クラス、ソート済みリスト、キューやスタック、多次元配列や行列は除外しています。
実装の「固定長配列」は確保されたメモリ領域が作成時のサイズ固定で変更されないこと、「可変長配列」は確保されたメモリ領域が作成時のサイズ固定ではなく、領域の拡張や縮小が出来るというと言うこと。ただし、拡張時に連続したメモリ領域を取れない場合は別の領域を新たに確保するようなrealloc
のような動作を想定しています。
Java
種類 | サイズ | サイズ取得 | 要素の型 | 実装 |
---|---|---|---|---|
配列 T[] | 実行時固定 | length | プリミティブ型と参照型 | 固定長配列 |
java.util.ArrayList | 可変 | size() | 参照型 | 可変長配列 |
java.util.LinkedList | 可変 | size() | 参照型 | 双方向リスト |
java.util.concurrent.CopyOnWriteArrayList | 可変 | size() | 参照型 | COW配列? |
CopyOnWriteArrayList
はスレッドセーフなArrayList
の実装ですが、どうから、Copy-On-Write(COW)の仕組みで要素を管理しているらしいです。詳しいところはソースコードまで確認していないので、ちょっとわからないのですが。
C
種類 | サイズ | サイズ取得 | 要素の型 | 実装 |
---|---|---|---|---|
配列 T[] | コンパイル時固定 | sizeof | 全て | 固定長配列 |
可変長配列(VLA) | コンパイル時固定 | sizeof | 全て | 固定長配列 |
動的メモリ確保(malloc等) | 可変 | ❌ | 全て | 可変長配列 |
sizeofは配列の要素数ではなく、領域のサイズになりますので、要素数を求めるには型のサイズで割る必要があります。
Visual C++のように可変長配列(VLA)を実装していないコンパイラもあります。なお、この可変とはコンパイル時に対して可変という意味で、配列作成後に可変という意味ではありません。
動的メモリ確保は配列を作るのでは無く、メモリ領域を確保するだけになります。確保したメモリ領域を配列と同じようにアクセスできると言うだけに過ぎません。
C++
種類 | サイズ | サイズ取得 | 要素の型 | 実装 |
---|---|---|---|---|
配列 T[] | コンパイル時固定 | sizeof | 全て | 固定長配列 |
動的メモリ確保(malloc等) | 可変 | ❌ | 全て | 可変長配列 |
std::array | コンパイル時固定 | size | 全て | 固定長配列 |
std::vector | 可変 | size | 全て | 可変長配列 |
std::foward_list | 可変 | size | 全て | 片方向リスト |
std::list | 可変 | size | 全て | 双方向リスト |
C++には可変長配列(VLA)がありません。
C#
種類 | サイズ | サイズ取得 | 要素の型 | 実装 |
---|---|---|---|---|
配列 T[] | 実行時固定? | Length | 全て | 固定長配列? |
System.Collections.Generic.List | 可変 | Count | 全て | 可変長配列 |
System.Collections.Generic.LinkedList | 可変 | Count | 全て | 双方向リスト |
C#ではArray.Resize()
で配列のサイズを変更可能のように見えます。しかし、配列は参照型であるにもかかわらず、Array.Resize()
では参照渡し(ref
付き)でなくてはならない所から、配列自体を変えると言うより変数を置き換えるという動作のようです。
C#
1public class Test 2{ 3 public static void Main() 4 { 5 int[] x = {0, 1, 2, 3}; 6 System.Console.WriteLine(x.Length); // => 4 7 System.Array.Resize(ref x, 5); 8 System.Console.WriteLine(x.Length); // => 5 9 OtherResize6(x); 10 System.Console.WriteLine(x.Length); // => 5 のまま 11 System.Console.WriteLine(x[0]); // => 10 変更されている。 12 System.Console.WriteLine(x[1]); // => 1 変更されていない。 13 } 14 public static void OtherResize6(int[] a) 15 { 16 a[0] = 10; 17 System.Array.Resize(ref a, 6); 18 a[1] = 20; 19 } 20}
配列は参照型ですので、通常は共有渡しになります。上記のように共有渡ししたあとにArray.Resize()
をしても、元々のオブジェクトには影響を与えていません。その後の要素変更も反映されなくなっています。
私もよくわからないので、あとはC#に詳しい人に聞いてください。
さて、同じ配列と言っても、各言語によってその性質は微妙に異なる物です。同じ固定長であっても、コンパイル時固定と実行時固定は出来ることに大きな違いがあります。実行時固定は新たな配列を作り直してコピーすることで可変な配列のように扱えないわけではありません。C#のArray.Resize()
がまさしくそのような動作です。しかし、コンパイル時固定の場合、入力などによってサイズが変わるようなデータを扱うことはほぼ不可能です。つまり、Javaの配列で出来ることがそのままC++の配列でも出来るとは限らないと言うことです。
次に、Java特有の問題について書きましょう。JavaのArrayList
等はint
等のプリミティブ型を扱えません。普通の整数の可変長なりストを作りたい…と言う場合はArrayList<Integer>
と言う風にラッパークラスを使うしか無いと言うことです。現在はオートボクシング機能があるため、以前よりは書きやすくなったとは言え、ラッパークラスの取り扱いは非常に面倒です。また、四則演算するだけでもプリミティブ型とラッパークラスの間に相互変換が入ります。ある程度はキャッシュしてくれるとは言え、オブジェクトが作成を繰り返すことになるため、少なくないコストがかかります。言ってしまえば、int[]
とArrayList<Integer>
では、無視できないぐらいのパフォーマンスの差があると言うことです(参照: java - Lists, primitive types, and performance - Stack Overflow)。
こういった側面を考えて、Javaではなるべく配列を使う(特にプリミティブ型では)というのはありだと思います。ただ、Java 8以降はint
をそのままストリームにするIntStream
等を(コードの内容によりますが)代替に使うのもありかと思います。
これに比べて、C++のstd::vector
は、どのような型でも扱えますし、内部の実体は通常の配列と同じようになっているため、パフォーマンスが急激に落ちると言った所がありません。むしろ、コンパイル時固定の配列は使用できるところが限られるという面もあります。だから、ほとんどの場合はstd::vector
を使用しているように思います。
なお、コンパイル時固定でも構わないという場合は、インタフェースの共通性からstd::array
を使うことの方が多いような気がします。私の場合、配列特有の動作(コンストラクタとかデストラクタとか、配列に合わせた動きをするようにできています)が勉強不足で理解できていないから、近づかないように避けているだけって事情もありますが…。
投稿2018/05/18 23:18
総合スコア21739
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2018/05/19 00:19
2018/05/19 01:21
0
そもそも、C/C++言語においては'配列'というのは特例の塊みたいなもんなのです。
c++
1#include <iostream> 2using namespace std; 3void print1(int a[]){ cout << sizeof(a) << endl; } 4void print2(int a[4]){ cout << sizeof(a) << endl; } 5void print3(int a[0]){ cout << sizeof(a) << endl; } 6int main(){ 7 int a[4] = {0}; 8 cout << sizeof(a) << endl; // 16 9 print1(a); // => 8 10 print2(a); // => 8 11 print3(a); // => 8 12 cout << sizeof(int*) << endl; // => 8 13 // int b[4] = a; // コンパイルエラー 14}
これがSTLのarrayを用いることで
c++
1#include <iostream> 2#include <array> 3#include <vector> 4using namespace std; 5void print1(vector<int> a){ cout << a.size() << endl; } 6void print2(array<int, 4> a){ cout << a.size() << endl; } 7void print3(array<int, 0> a){ cout << a.size() << endl; } 8int main(){ 9 array<int, 4> a{4,3,2,0}; 10 cout << a.size() << endl; // 4 11 print1(vector<int>(begin(a), end(a))); // => 4 12 print2(a); // => 4 13 // print3(a); // コンパイルエラー 14 15 array<int, 4> b = a; 16 for(auto& i : b){ cout << i; } // => 4320 17}
と、大分直感的になります。
今後、C++においてはarrayの方を配列と呼ぶ事になると嬉しいという希望的推測
(ついでにvectorへの変換もうちょいスムーズになって欲しい)
投稿2018/05/18 23:02
編集2018/05/18 23:03総合スコア15149
0
C++で配列[]
装置相手にする際、最大メモリサイズも決まっているのでよく使っています。
またH8でもC++使うことありますけど、vectorなどテンプレートライブラリを組み込むメモリなぞ余ってないので配列は使ってます。
投稿2018/05/18 21:24
総合スコア187
0
こんにちは。
Javaでの開発は要素数が事前にわかっていれば極力配列を使えと教えられてきました
C++でも同じです。コンパイルする時までに要素数が判っているなら配列、もしくは、std::arrayを使うのが好ましいです。
Javaには詳しくないのですが、もしJavaの配列がC#の配列と同じようなものなら、C++ではstd::unique_ptr<T[]>がほぼ同等です。
ああ、yohhoyさんの回答をみて、要素数の管理のことを忘れていることに気が付きました。std::arrayなら要素数も管理してくれます。ややこしいけどstd::unique_ptr<std::array<T, N>>
かな。
投稿2018/05/18 13:42
編集2018/05/18 13:44総合スコア23272
0
ベストアンサー
すでに出そろっている感がありますが、私の場合もC++では素の配列を使うことは減りましたね。可変サイズにしたいときや大量のデータを扱うときはほぼvector<>
ですし、固定サイズはarray<>
が便利なのでそれを使います。
配列を使ってもっとレガシーな感じで書きたいのですが
それに何の意味があるのかよく判りません。意図せぬバグの温床となること請け合いです。素の配列はサイズの管理が煩雑になりがちですし、範囲チェックも設計者の責任で行わなければいけません。
ただし、素の配列が便利なケースもあります。例えば、テンプレートとの組み合わせで素の配列でもサイズ管理が可能な場合ですね。
c++
1template <size_t N> 2void function(int (&data)[N]) 3{ 4 std::cout << "N=" << N << std::endl; 5 for(auto x : data) 6 { 7 std::cout << x << std::endl; 8 } 9} 10 11int main() 12{ 13 int a[]{1, 2, 3}; 14 function(a); 15 int b[]{1, 2, 3, 4, 5, 6}; 16 function(b); 17 return 0; 18}
array<>
では上記配列a
,b
のような初期化ができないので、そのような書き方をしたい場合は素の配列を使うことがあります。
投稿2018/05/19 00:33
総合スコア5944
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2018/05/19 01:36
2018/05/19 02:03 編集
2018/05/19 02:32
2018/05/22 01:41 編集
0
配列って、後から、変更できないとか、色々と不便だからではないでしょうか?
また、初期のCの頃だと、配列でのアクセスが効率良かった、という面もあると思いますが、今は、その程度の効率より、使いやすさが優先していると思っています。
あ、構造体とかの配列をポインタでアクセスすると、よく間違えるってものあった思います。
それらを含めて、配列が推奨されにくいのかも知れません。
投稿2018/05/18 13:11
総合スコア6385
0
解決済みですが数点
javaとかC++の流儀ということもありますが、結局は組織のルール(要するに上司の好み)で方針が変わると言うことはザラにあります。同じやり方なのに上司が変わっただけでダメになるなんて日常茶飯事です。
一方が他方の完全上位互換というわけでない以上、一長一短であり、好みでやり方が変わることは当然です。まあ、困ったらSTL使っておけばとりあえずは問題ないと思います。「固定長」であるという前提があったとしても、これが覆ることも多々ありえますし。ただし、デバッグ時の絞り込みが難しくなる欠点がありますので、これをどう考えるか次第だと思います。
もっとも、上司や取引相手と早い段階で仕様確定できればそれに越したことはありません。まあ、「こんなこと自分で考えろ」という人もいますけどね。どのみち「人次第」の要素が大きいですね。
投稿2018/05/19 07:10
総合スコア4830
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。