LoginSignup
36
43

C++ では C言語由来 の多次元配列を使うべき理由がない。

Last updated at Posted at 2015-08-16

ご指摘頂いたコメントにしたがって、タイトルを変更いたしました。

「引き継いだソースコードをメンテナンスしやすくする。」
http://qiita.com/nonbiri15/items/3417facd391e01c3e365
の続編です。

こんなコードがソースコードの中にありませんか?
double data[SIZE_Y][SIZE_X];
もし、こんなコードがあったら、OpenCVのcv::Mat型やEigenなどを用いて書き換えましょう(注)。

####C言語由来の2次元配列の引き起こす課題
double型の[SIZE_Y][SIZE_X]個の要素を持つ2次元配列は次のように書かれていることがある。
double data[SIZE_Y][SIZE_X];

このデータ形式を使うと次のようなたくさんの欠点がある。
・静的にメモリ確保するときには、マクロ定数が必要になる。
 そしてこのマクロ定数が入ったヘッダファイルをいろんなファイルでincludeすることになる。
・C言語由来の多次元配列のポインタと多次元配列の大きさの関連付けがない。
 上記のdouble型のデータは行列をおいてあるアドレスを開始位置に、SIZE_Xの要素が1行をなし、それがSIZE_Y個並んでいるように使うことを、プログラマが、コンピュータに教えてやっているのだが、行列のおいてあるアドレスだけを受け取ったときに、コンピュータの側では、いくらdouble型の値が並んでいるのかをまったく知る余地がない。

・void func(double data[][SIZE_X]);のように書かなくてはならない。
 一次元の配列ならば
void func1(double data[]);
のように書くことができるが、2次元の配列では、上記のSIZE_Xの記述を加えなくてはならない。このことは
double sum(double mat[][SIZE_X]);
のように書かなくてはならなくなるので、マクロ定数SIZE_Xを含んだ関数の宣言になってしまっている。任意の大きさの2次元配列の要素のsumやmeanやその他の演算の関数が素直に書けないという制限を持っている。

OpenCVのcv::Mat型であれば、以下のように書け、関数の引数が簡単になる。
//実際には、この例の関数を使うよりは、OpenCVのライブラリに実装されているメソッドを使うのがよい。

example.cpp
double sum(const cv::Mat &data){
    double r=0.0
    for(int y = 0; y < data.rows; y++){
        for(int x = 0; y < data.cols; x++){
            r += data.at<double>(y,x);
        }
    }
    return r;
}

 このため、cv::Mat型のようなデータ構造を使わないと、広い範囲でSIZE_Xのマクロ定数を残す羽目になる。
・標準的なデータ形式を使うと信頼性の高い標準的な関数群を使うことになるので、メンテナンスすべきコードの量が減る。

・double data[SIZE_Y][SIZE_X];の領域を動的に確保するのが面倒。
 そのやり方は以下のページに記されているので、ここではその詳細を省く。 
もちろん、動的に確保した領域は、動的に解放しなくてはならない。それらの利用によってメモリリークを生じないようにする注意が必要だ。
「C言語で2次元配列を動的に割り当てる4つの方法」
http://tondol.hatenablog.jp/entry/20090713/1247426321

・動的に確保した多次元配列[][]は、ネストされた配列にすぎず、基本的には大きさがそろっている保証がない。(上の指定の仕方では、そろっているが。)

 20年前なら数値計算のためのC/C++でのライブラリが少なく、"Numerical Recipes in C"などのお世話になったが、C++言語のライブラリが充実している現在に、このような形で線形代数のために多次元配列を使うべき理由がない。

####例:線形代数の枠組みとしてのOpenCV
画像データだけではなく、一般の多次元の数値データを扱うことができる。
CV_32F, CV_64Fなどの実数が扱えるだけではなく
CV_32FC2 は 2 チャンネル(つまり複素数)浮動小数点型の配列を表します.
(複素数の行列のライブラリとして充実の度合いは、私は未調査)

OpenCVは、画像屋用のライブラリというだけではなく、数値解析用のデータ形式とライブラリ群として用いることができる。線形代数の代表的なライブラリがあって、そのデータを可視化するライブラリがOS非依存で用意されているライブラリと見ることができる。OpenCVのライブラリはOpenMPやTBBなどに対応していて、しかもCUDAやOpenCVでGPGPUを使いたいときにも、線形代数で恩恵を受けることができる。
 私が不勉強なせいもあるのだろうけれど、ユーザーが多い線形代数のC++用のライブラリはOpenCVだと思っている(画像分野で)ユーザーが多い線形代数のC++用のライブラリの1つはOpenCVだと思っていた。他に線形代数のライブラリはあるので、それぞれの特徴を勉強しなければ。 :sweat:

Eigen
http://eigen.tuxfamily.org/index.php?title=Main_Page

Boost
boost::numeric::ublas
http://www.boost.org/doc/libs/1_36_0/libs/numeric/ublas/doc/matrix.htm
 
訂正(2015/08/17):
行列演算の分野で、Eigenというライブラリが広く用いられているというご指摘を受けましたので、上記の通り修正いたしました。

 
追記:多次元配列にOpenCVを使うべき理由
 EigenがC++の分野で広く使われている数値計算のライブラリであるが、にもかかわらずOpenCVを推奨したいシーンは、ZynqなどのFPGAデバイスを用いるシーンだ。OpenCVが広く画像処理・画像認識の分野で使われていることから、画像に関連する組み込み分野ではOpenCVを標準的なデータ形式とする組み込み分野のソリューションが存在している。ZynqなどのFPGAデバイスがcv::Mat型やIplImage型との親和性のいいデータ形式を専用ハードウエアで実行できるようにしている。画像に限らない行列データの線形代数もそれらのデバイスで高速化できる可能性があるだろう。

注:もちろん、それらのモジュールを改版してメンテナンスする必要があるときです。十分に枯れていて問題がないのであれは、改版のために改版すべきとは思いません。OpenCVやEigenなどのライブラリを使うにはそれらをインストールする必要を生じるのですから、十分に現状のソースコードが枯れていて問題を生じないときには、OpenCVやEigenを使うべきとは勧めません。ただ、そのような数値関係の部分がさらに書き加えていく必要がある場合には、C++の多次元配列を置き換えていくことが、結果として全体の開発やメンテナンスの工数を減らすことにつながると思います。

追記:C言語の範囲で行列計算をしたい場合
 C言語の範囲で行列計算をしたい場合には、どの程度本格的なライブラリが必要になるのかに応じて、信頼性の高いライブラリを使うことです。そうすると行列の表現は自然と決まります。

以下のURLでは、C言語の範囲で行列計算をするときのいくつかの手法を示し、それぞれの長所と欠点とを示しています。
 
C言語で行列を扱う

  
qiita C++を使って数値計算をしている学部生へのアドバイス

36
43
6

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
36
43