自動微分法も、複素数と同様に、付加情報を持った演算を行うことによって 勾配と言う有益な情報を得る。 区間演算も、丸め誤差を得たり、値域を得たりすることが出来る。
このような「インテリジェントな数値型」を使った演算を行うには、 演算子多重定義の活用が便利である。 C++においてそれを行う方法について解説する。
例題として、ごく普通の複素数型を実現するクラスの実装例を挙げる。 簡単のため、加算、減算、乗算のみ。 complex.hppがクラスの定義部で、 complex.cppがその使用例。
#ifndef COMPLEX_HPP #define COMPLEX_HPP ... #endif // COMPLEX_HPP |
は、ヘッダファイルの多重読み込み対策。複数回includeした場合、2度目以降は 読み込まれないようにする。
class complex { ... }; |
でクラス定義。クラスは、構造体の定義とそれを操作する関数(メソッド)群の定義を 同時に行うようなもの。 なお、ヘッダファイルではメソッドのプロトタイプ宣言のみを行い、その実装は 別の.cc(.cpp)ファイルで行うのが行儀がいいとされるが、 後述するテンプレートを使う場合は実装を別に書くのは難しく、 ここではヘッダファイルに全ての実装を書く流儀で統一する。
public: double re; double im; |
reとimがメンバ変数。いわゆる構造体のメンバに当たる。 なお、ここではpublic:指定でreとimが外部から丸見えになっているが、 オブジェクト指向の思想では適当なアクセサ(アクセスするためのメソッド)を準備し メンバ変数そのものはprivate:指定で隠蔽するのが普通。
complex() { } complex(const double& x) { re = x; im = 0.; } complex(const double& x, const double& y) { re = x; im = y; } |
戻り値の無いこれら3つのメソッドは、コンストラクタ。 それぞれ、引数無し、引数がdouble1つ、引数がdouble2つでインスタンスが 生成されたときの初期化のために呼び出される。 インスタンスの生成の仕方は、
complex x, y, z; x = complex(1.); y = complex(1., 2.); z = 1.; complex p(1.); complex q(1., 2.); complex r = complex(1.); complex s = complex(1., 2.); complex t = 1.; |
このようにいろいろな方法で行うことが出来る。
引数がdouble1つのコンストラクタは、「変換コンストラクタ」と呼ばれ、 double型からcomplex型への型変換に用いられる。 使用例で単に数値(1.)を代入できているのは、この変換コンストラクタの 働きによる。
なお、コンストラクタの引数をconst double&にしているのは、
friend complex operator+(const complex& x, const complex& y) { complex r; r.re = x.re + y.re; r.im = x.im + y.im; return r; } |
で、二項演算子「+」を定義している。 すなわち、
complex x, y, z; z = x + y; |
のようなcomplexとcomplexの加算において、この関数が呼ばれる。
次の、
friend complex operator+(const complex& x, const double& y) { complex r; r.re = x.re + y; r.im = x.im; return r; } friend complex operator+(const double& x, const complex& y) { complex r; r.re = x + y.re; r.im = y.im; return r; } |
は、
z = x + 1.; z = 2. + x; |
のように片方がdoubleの場合用の加算を定義している。 これは定義しなくても、変換コンストラクタが働くので一応実行可能である。 但し、その場合、x+1.は1を変換コンストラクタで1+0iに変換し、それをxに 加えるという動作になるので、無駄な計算が生じてしまう。このような場合は、 個別に定義した方がよい。
なお、これらの定義に付いている「friend」というキーワードは、これらが 「メンバ関数では無いがメンバ変数にアクセスが可能」という指定を行っている。 例えばcomplex同士のoperator+をメンバ関数として定義すると、
complex operator+(const complex& y) const { complex r; r.re = re + y.re; r.im = im + y.im; return r; } |
のようになる。すなわち、「z = x + y;」と書いたときに左側のxを経由してoperator+が 呼ばれる。しかし、このような書き方だと、「z = 2. + x;」のように演算子の左側が complexで無かった場合に困る。 このため、二項演算子はあえてメンバ関数ではなく一般の関数とし、friend指定で アクセスを許可するという書き方をするのが一般的である。
friend complex& operator+=(complex& x, const complex& y) { x = x + y; return x; } friend complex& operator+=(complex& x, const double& y) { x.re += y; return x; } |
これは、演算子+=を実装している。xを書き換えるので、xは参照かつconstではない。 また、xの参照を返すのは、
p = (z += 2.); |
friend complex operator-(const complex& x, const complex& y) { complex r; r.re = x.re - y.re; r.im = x.im - y.im; return r; } friend complex operator-(const complex& x, const double& y) { complex r; r.re = x.re - y; r.im = x.im; return r; } friend complex operator-(const double& x, const complex& y) { complex r; r.re = x - y.re; r.im = - y.im; return r; } friend complex& operator-=(complex& x, const complex& y) { x -= y; return x; } friend complex& operator-=(complex& x, const double& y) { x.re = x.re - y; return x; } |
このへんは減算。基本的に加算と同じ。
friend complex operator-(const complex& x) { complex r; r.re = - x.re; r.im = - x.im; return r; } |
これは単項演算子の「-」。「p = -z」とか。
friend complex operator*(const complex& x, const complex& y) { complex r; r.re = x.re * y.re - x.im * y.im; r.im = x.re * y.im + x.im * y.re; return r; } friend complex operator*(const complex& x, const double& y) { complex r; r.re = x.re * y; r.im = x.im * y; return r; } friend complex operator*(const double& x, const complex& y) { complex r; r.re = x * y.re; r.im = x * y.im; return r; } friend complex& operator*=(complex& x, const complex& y) { x = x * y; return x; } friend complex& operator*=(complex& x, const double& y) { x.re *= y; x.im *= y; return x; } |
乗算も一応定義した。基本的に加減算と同じ。
friend complex sqr(const complex& x) { complex r; r.re = x.re * x.re - x.im * x.im; r.im = 2. * x.re * x.im; return r; } |
演算子では無いが、数学関数の実装の例としてのsqr(自乗)。
friend std::ostream& operator<<(std::ostream& s, const complex& x) { s << '(' << x.re << '+' << x.im << "i)"; return s; } |
これは、<<演算子。
std::cout << z << "\n"; |
のように普通に書けるようになる。