自動微分法も、複素数と同様に、付加情報を持った演算を行うことによって 勾配と言う有益な情報を得る。 区間演算も、丸め誤差を得たり、値域を得たりすることが出来る。
このような「インテリジェントな数値型」を使った演算を行うには、 演算子多重定義の活用が便利である。 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";
|
のように普通に書けるようになる。