はじめに
これは、主に大学に入ってからプログラミングを始め、今授業や研究でC++を使って行列の演算を含む数値計算をしている学部生を対象に書いています。
きっかけは、江添亮さんの歌舞伎座.tech #8 「C++初心者会」を開催したというブログの
大学でC++03を教わった私が、便利そうだと思ったC++11の新機能
これも初心者らしい話。聞説、発表者の大学では、この2015年に大昔の化石規格であるC++03を教えて、それで学位を与えているようだ。日本の教育機関は10年ほど前からC++標準化委員会と関わらなくなっているため、もはや日本の教育機関に最新のC++規格をまともに把握している人間はいない。そのため、C++11を教育できる人間がいないのだろう。
この2015年にC++03しか教育できない教育者しかいない大学というのは何なのだろうか。しかも発表者によると、教育内容には規格上の誤りが多かったという。
とnew
/delete
を使って多重配列を定義している記事を読んだことです。
本当にC++を使って計算する必要がありますか
C++を使った線形代数計算の話に入る前に、まず本当にC++を使う必要があるのかよく考えてください。
Python, Matlab, Julia等の、C++よりもプログラミングの難易度が低くて簡単に線形代数計算ができる言語があります。
そちらの方が幸せになれます。
C++を数値計算に利用する理由はただひとつ「Performance」 です。
C++ doesn't give you performance. It's not gonna magic up some performance that make your program faster. Java isn't to give you performance either. The thing that C++ gives you, that I think particularly valuable, is control over the performance of your application. When you need to fix a performance problem in C++, the means to fix it are fairly direct, available, and they tend to not ruin your software. And Java, this is actually a lot harder, I feel. I think this is where we have perception of a performance difference between Java and C++. It's much less to do with the intrinsic speed of the languages. It is much more to do with how easy it is to take control over your performance.
CppCon 2014: Chandler Carruth "Efficiency with Algorithms, Performance with Data Structures"
new
/delete
を使った動的配列のメモリ確保・解放のことは忘れましょう
Bjarne StroustrupがC言語の拡張として開発したC++は
表現力と効率性の向上のために、手続き型プログラミング・データ抽象・オブジェクト指向プログラミング・ジェネリックプログラミングの複数のプログラミングパラダイムを組み合わせている
[プログラミング言語C++ 第4版, pp.12-13]
という特徴を持っています。C++の備える特徴を最大限に活用することで、プログラマーはリソースの確保・解放といった直接的・具体的な操作を気にせずに、数式を書くかのようにプログラムできます。
残念ながら、大学の工学部でのプログラミング教育はFortranやCを基本としており、最新のC++について学ぶことができる授業は(私の知る限り)ほとんどありません。したがって、自分で積極的にプログラミングを勉強しない限り、化石時代のプログラム
int size = /* ... */
// メモリ確保
double* a = new double[size];
double* b = new double[size];
double* c = new double[size];
/* ... 配列の初期化 ... */
// ベクトル和:c = a + b
for (int i = 0; i < size; ++i)
c[i] = a[i] + b[i];
// メモリ解放
delete [] a;
delete [] b;
delete [] c;
を書くことになります。こんなプログラムを書くくらいならPythonで
import numpy as np
a = np.array([1, 2, 3, 4])
b = np.array([3, 0, -1, 2])
c = a + b
# c = [4, 2, 2, 6]
と書くほうが、作る方も読む方も幸せになれます。
ちなみにCppConのプレゼンにこんなの↓がありました。
CppCon 2015: Kate Gregory “Stop Teaching C"
C++では自分でnew
/delete
したら負けです
C++ Core Guidelines, R: Resource Managementを読み、最新のC++におけるリソース管理の指針について勉強しましょう。
To avoid leaks and the complexity of manual resource management. C++'s language-enforced constructor/destructor symmetry mirrors the symmetry inherent in resource acquire/release function pairs such as fopen/fclose, lock/unlock, and new/delete. Whenever you deal with a resource that needs paired acquire/release function calls, encapsulate that resource in an object that enforces pairing for you -- acquire the resource in its constructor, and release it in its destructor.
以下和訳
リークや手動での複雑なリソース管理を避けること。C++が言語レベルで強制するコンストラクタ/デストラクタの対称性は、fopen/fclose、lock/unlock、new/deleteのようなリソースの確保・解放をする関数に内在する対称性を反映しています。リソース管理に常にリソース確保・解放関数の呼び出しをペアで行わなければならない場合は、代わりに対となる操作を強制させるオブジェクトにリソースを隠蔽してください。すなわち、コンストラクタ内でリソースを確保し、デストラクタの中で解放するということです。
つまり、配列を扱うなら、少なくともstd::array
かstd::vector
を使えということです。
最初からEigen・Armadillo・xtensorなどのC++用線形代数ライブラリを使うようにする
Eigen・Armadillo・xtensor等のC++用の線形代数ライブラリを使って、自分でメモリの確保・解放をしたり、要素ごとにforループを回さずに済むようにしましょう。特にEigenはヘッダーのみのライブラリでインクルードするだけで使えるためおすすめです。
#include <Eigen/Core>
/* ... */
// 要素の型がdoubleで、大きさを変更可能なベクトル
using Eigen::VectorXd;
int size = /* ... */
// コンストラクタが呼ばれ、自動的にベクトルa,bのリソースが確保される
VectorXd a(size);
VectorXd b(size);
/* ... ベクトルa, bの初期化 ... */
// ベクトル和:c = a + b
// 自分でforループを回す必要はない。
VectorXd c = a + b;
// ちなみに、EigenはExpression Templateという技法を使っているため、
// 上の式でautoを使うとcの型がVectorXdではなくなってしまう。
// スコープを抜ければ自動的にa,b,cのリソースは解放される