講義の演習課題メモとして残しておきます。
詰まったのが、返り値を統一初期化記法で返す時に、『コピーコンストラクタ→コンストラクタ』で呼び出されるということ...。
コピーコンストラクタが呼び出されたら、コンストラクタ呼び出されないと思ってた...。
#include <iostream> #include <stdio.h> #include <math.h> using namespace std; class Complex { private: double re ; double im ; public: // ========== コンストラクタ ========== // Complex( const double r, const double i) : re(r), im(i) {} // コピーコンストラクタ Complex(const Complex &obj) = default; Complex() : re(0), im(0) {} // ========== デストラクタ ========== // ~Complex() = default; // ========== 関数群 ========== // // 表示 void print() { if (im >= 0) { printf("%lf + j%lf\n", re, im); } else { printf("%lf - j%lf\n", re, abs(im)); } } // 実部 double get_re() { return re; } // 虚部 double get_im() { return im; } // 絶対値 double get_abs() { return sqrt(re * re + im * im); } // ========== 演算子オーバーロード ========== // // 加算 Complex operator + (Complex num) { double _re = this->re + num.re; double _im = this->im + num.im; return {_re, _im}; } // 減算 Complex operator - (Complex num) { double _re = this->re - num.re; double _im = this->im - num.im; return {_re, _im}; } // 乗算 Complex operator * (Complex num) { double _re = this->re * num.re - this->im * num.im; double _im = this->re * num.im + this->im * num.re; return {_re, _im}; } // 除算 Complex operator / (Complex num) { double _re = (this->re * num.re + this->im * num.im) / num.get_abs(); double _im = (this->re * num.im - this->im * num.re) / num.get_abs(); return {_re, _im}; } } ; class __Complex { private: double r ; double th ; public: // ========== コンストラクタ ========== // __Complex(const double re, const double im) { r = sqrt(re * re + im * im); th = atan2(im, re); } // コピーコンストラクタ __Complex(const __Complex &obj) { r = obj.r; th = obj.th; } __Complex() : r(0), th(0) {} // =========== デストラクタ ============ // ~__Complex() = default; // ========== 関数群 ========== // // 表示 void print() { printf("%lf ∠ %lf\n", r, th); } // 実部 double get_re() { return r * cos(th); } // 虚部 double get_im() { return r * sin(th); } // 絶対値 double get_abs() { return r; } // 偏角 double get_arg() { return th; } // 極座標系 → 直交座標系 Complex to_cart() { double _re = r * cos(th); double _im = r * sin(th); return {_re, _im}; } // ========== 演算子オーバーロード ========== // // 加算 __Complex operator + (__Complex num) { Complex _left = this->to_cart(); Complex _right = num.to_cart(); Complex _sum = _left + _right; return {_sum.get_re(), _sum.get_im()}; } // 減算 __Complex operator - (__Complex num) { Complex _left = this->to_cart(); Complex _right = num.to_cart(); Complex _sub = _left - _right; return {_sub.get_re(), _sub.get_im()}; } // 乗算 __Complex operator * (__Complex num) { double _r = this->get_abs() * num.get_abs(); double _th = this->th + num.th; __Complex _temp; _temp.r = _r; _temp.th = _th; return _temp; } // 除算 __Complex operator / (__Complex num) { Complex _left = this->to_cart(); Complex _right = num.to_cart(); Complex _div = _left / _right; return {_div.get_re(), _div.get_im()}; } } ; int main () { // ==================================================== printf("==================== 直交座標クラス ======================\n"); // z = √3 + j Complex z(sqrt(3), 1); printf("z = √3 + j : "); z.print(); // 1.732051 + j1.000000 // za = 0.5 * (√3 + j) Complex za(sqrt(3)/2, 1.0/2); printf("za = 0.5 * (√3 + j) : "); za.print(); // 0.866025 + j0.500000 // zb = 1 + j√3 Complex zb = z * za; printf("zb = z * za = 1 + j√3 : "); zb.print(); // 1.000000 + j1.732051 // zz = -√3 - j Complex zz(-sqrt(3), -1); printf("zz = -√3 - j : "); zz.print(); // -1.732051 - j1.000000 // zza = -0.5 * (√3 + j) Complex zza(-sqrt(3)/2, -1.0/2); printf("zza = -0.5 * (√3 + j) : "); zza.print(); // -0.866025 - j0.500000 // zzb = 1 + j√3 Complex zzb = zz * zza; printf("zzb = zz * zza = 1 + j√3 : "); zzb.print(); // 1.000000 + j1.732051 printf("=======================================================\n"); // ==================================================== // ==================================================== printf("====================== 極座標クラス =====================\n"); // z1 = 2∠(pi/6) // z1 = 2{cos(pi/6) + j * sin(pi/6)} __Complex z1(sqrt(3), 1); printf("z1 = 2∠(π/6) : "); z1.print(); // 2.000000 ∠ 0.523599 // z2 = 1∠(pi/6) // z2 = cos(pi/6) + j * sin(pi/6) __Complex z2(sqrt(3)/2, 1.0/2); printf("z2 = 1∠(π/6) : "); z2.print(); // 1.000000 ∠ 0.523599 // z3 = 2∠(pi/3) __Complex z3 = z1 * z2; printf("z3 = z1 * z2 = 2∠(pi/3) : "); z3.print(); // 2.000000 ∠ 1.047198 printf("z3 = z1 * z2 = 1 + j√3 : "); z3.to_cart().print(); // 1.000000 + j1.732051 // zz1 = 2∠(-pi/6) __Complex zz1(-sqrt(3), -1); printf("zz1 = -√3 - j : "); zz1.print(); // 2.000000 ∠ -2.617994 // zz2 = = 1∠(-pi/6) __Complex zz2(-sqrt(3)/2, -1.0/2); printf("zz2 = -0.5 * (√3 + j) : "); zz2.print(); // 1.000000 ∠ -2.617994 // zz3 = 2∠(-pi/3) __Complex zz3 = zz1 * zz2; printf("zz3 = zz1 * zz2 = 2∠(-pi/3) : "); zz3.print(); // 2.000000 ∠ -5.235988 printf("zz3 = zz1 * zz2 = 1 + j√3 : "); zz3.to_cart().print(); // 1.000000 + j1.732051 printf("=======================================================\n"); // ==================================================== return 0; }
考察点
極座標クラスで(1)のように演算子をオーバーライドしてみた。
// (1) // 乗算 __Complex operator * (__Complex num) { double _r = this->get_abs() * num.get_abs(); double _th = this->th + num.th; return {_r, _th}; }
この状態で(2)のような計算を行う。
// (2) __Complex z1(sqrt(3), 1); // z1 = 2∠(pi/6) __Complex z2(sqrt(3)/2, 1.0/2); // z2 = 1∠(pi/6) __Complex z3 = z1 * z2; // z3 = 2∠(pi/3)
この計算が正常に行われているならば、z3を出力すると
// (3) z3.print(); // 2.000000 ∠ 1.047198 z3.to_cart().print(); // 1.000000 + j1.732051
(3)のように表示されなければならない。しかし、(1)のように定義した状態では(4)のような出力結果となる
// (4) z3.print(); // 2.257570 ∠ 0.482348 z3.to_cart().print(); // 2.000000 + j1.047198
この原因が、演算子オーバーライドメソッドでの返り値の返し方に問題があることが分かった。(1)のreturn文はC++11で新たに追加された『統一初期化記法(Uniform Initialization)』を利用している。統一初期化記法を用いてreturnする場合、返り値クラスで初期化されたオブジェクトが returnされる。(この場合、__Complex) つまり、『r, th』で計算済みのオブジェクトを統一初期化記法でreturn すると、__Complexのコンストラクタによって、再計算されたオブジェクトがreturn されることになる。そのため演算子オーバーライドメソッド内に局所変数でreturnオブジェクトを作成することで回避した。
// (5) // 乗算 __Complex operator * (__Complex num) { double _r = this->get_abs() * num.get_abs(); double _th = this->th + num.th; __Complex _temp; _temp.r = _r; _temp.th = _th; return _temp; }
統一初期化記法(Uniform Initialization, Universal Initialization)
C++11から、変数、配列、構造体、STLコンテナに関わらず、同じように初期化できるようになった。この{…}を使った統一的な初期化表現を『Uniform Initialization』または『Universal Initialization』と呼ぶ。この初期化方法はC++を開発したBjarne Stroustrup(ビャーネ・ストロヴストルップ)氏も推奨している。
// == 従来の書き方 == // "=" を使って int x = 3; // "= {}" を使って int a[] = { 0, 1, 2, 3 }; // これも "= {}" を使って struct S1 { int a, b; } s = { 0, 1 }; // ループを使って。 std::vector<int> v; for(int i = 0; i < 4; ++i) v.push_back(a[i]);
// == 統一的な書き方 == int x { 3 }; int a[] { 0, 1, 2, 3 }; struct S1 { int a, b; } s { 0, 1 }; std::vector<int> v { 0, 1, 2, 3 };
しかし、コンストラクタにexplicitがあるクラスでは、暗黙キャストとして扱われる統一初期化はコンパイルエラーとなり使用できない。
tuple<int, char> createTuple(void) { return { 1, 'a' }; // Compile error }
explicit
『explicit指定子』とは、コンストラクタが明示的であること(暗黙の変換、コピー初期化が使用できないこと)を指定する指定子。
struct A { A(int) { } // コンストラクタ。 A(int, int) { } // コンストラクタ (C++11)。 operator bool() const { return true; } }; struct B { explicit B(int) { } explicit B(int, int) { } explicit operator bool() const { return true; } }; A a1 = 1; // OK、コピー初期化は A::A(int) を選択します。 B b1 = 1; // エラー、コピー初期化は B::B(int) を考慮しません。 A a4 = {4, 5}; // OK、コピーリスト初期化は A::A(int, int) を選択します。 B b4 = {4, 5}; // エラー、コピーリスト初期化は B::B(int,int) を考慮しません。 bool na1 = a1; // OK、コピー初期化は A::operator bool() を選択します。 bool nb1 = b2; // エラー、コピー初期化は B::operator bool() を考慮しません。