ホーム » スタッフ » 斉藤徹 » 講義録 » オブジェクト指向 » 複素数クラスのプログラム例への質問

2022年4月
 12
3456789
10111213141516
17181920212223
24252627282930

検索・リンク

複素数クラスのプログラム例への質問

授業で扱った複素数クラスのプログラムについて、以下のようなプログラムだと、a.add( b ) を実行すると、a の値が書き換わる。このため、次に a.mul( b ) を実行すると、(3+j5) * (2+j3) を実行する。もっと直感的な結果になるように、a の値が書き換わらないようにできないのか? といった趣旨の質問があった。

class Complex {
private:
    double re , im ;
public:
    Complex( double r , double i ) : re( r ) , im( i ) {}
    void print() { printf( "%f+j%f\n" , re , im ) ; }
    void add( Complex z ) {
        re = re + z.re ;
        im = im + z.im ;
    }
    void mul( Complex z ) {
        double r = re * z.re - im * z.im ;
        double i = re * z.im + im * z.re ;
        re = r ;
        im = i ;
    }
} ;
int main() {
    Complex a( 1 , 2 ) , b( 2 , 3 ) ;
    a.add( b ) ;  // a = a + b ;
    a.print() ;   // 3 + j5
    a.mul( b ) ;  // a = a * b ; ← aの値はすでに3+j5に変わった後
    a.print() ;   // (6-15) + j(10+9) = -9+j19
}

a の値が書き換わらないようにしたいのなら、以下のようなコードになるだろう。

対象オブジェクトを変化させない書き方

class Complex {
     :
    Complex add( Complex z ) {
        return Complex( re + z.re , im + z.im ) ;
    }
    Complex mul( Complex z ) {
        return Complex( re * z.re - im * z.im ,    // Complex オブジェクトを作って
                        re * z.im + im * z.re ) ;  //   返り値として返す。
    }
} ;
int main() {
    Complex a( 1 , 2 ) , b( 2 , 3 ) ;
    a.add( b ).print() ;  // 3+j5
    a.mul( b ).print() ;  // (2-6)+j(3+4) = -4+j7
}

ただ、このコードは、add() や mul() が Complex オブジェクトを作って返り値を返すが、その新しいオブジェクトはどのように呼び出し側に返されるのか?誰が廃棄するの? といった点で、単純なC言語の知識だけでは動作を理解しづらいことから、最初のコードにて説明を行った。でも、後者の方が計算結果のイメージは直感的だし、return コンストラクタ(…) の書き方に慣れてしまえば、プログラムも読みやすい!!

const メソッドとオブジェクトの参照渡し

前者のプログラムは、add() により 対象オブジェクトに副作用が発生する。後者は対象オブジェクトは変化しない。メソッドを呼び出す際にも、対象オブジェクトに副作用が発生しないことを明示したconstメソッドとして定義することで、オブジェクトを間違って破壊することから守ることもできる。

また、add() , mul() の引数は void add( Complex z ) {…} のような書き方では値渡しが行われる。つまり、メソッド呼び出し時点で実引数を仮引数にコピーする処理が発生する。このため、処理効率を考えるとポインタ渡し(参照渡し)の方がムダなコピーが発生しない。

一方で、参照渡しを行うと、ポインタを経由して引数に副作用を及ぼすことも可能となるため、参照渡しに const 宣言をつけることで、引数によるオブジェクト破壊を防ぐことができる。

class Complex {
private:
    double re , im ;
public:
    Complex( double r , double i ) : re( r ) , im( i ) {}
    void print() const {                   // 表示だけで副作用は発生しない
        printf( "%f+j%f\n" , re , im ) ;
    }
    Complex add( const Complex & z ) const {  // 対象オブジェクトは変化しない
        //       ~~~~~        ~~~    ~~~~~
        //       zに副作用なし 参照渡  constメソッド
        return Complex( re + z.re , im + z.im ) ;
    }
    Complex mul( const Complex & z ) const {
        return Complex( re * z.re - im * z.im ,
                        re * z.im + im * z.re ) ;
    }
} ;
int main() {
    Complex a( 1 , 2 ) , b( 2 , 3 ) ;
    a.add( b ).print() ;  // 3+j5
    a.mul( b ).print() ;  // (2-6)+j(3+4) = -4+j7
}