2016年06月26日

演算子の多重定義

今日はそれほど暑くもなく、カラッとしたいい天気でした。しかし、明日からまた雨のようですね。

C++ には「演算子の多重定義」というものがあります。任意の型に対して、int 型や double 型などと同等に演算子が利用できるというものです。

SomeClass SomeClass::operator+( const SomeClass& s1, const SomeClass& s2 );

という関数を実装すれば、

SomeClass s1, s2;
:
// 何らかの方法で値を代入
:
SomeClass add = s1 + s2;

と書くことができます。四則演算だけではなく、等号・不等号演算子やポインタ・参照演算子なども多重定義できるので、うまく利用すれば非常に便利な反面、やみくもに使うと混乱の元とも言われています。例えば、ベクトルに対して積の演算子を多重定義した場合、それは内積なのか外積なのかというのは利用する場面によって変わります。こんなときは素直に innerproduct, outerproduct と通常の関数を用意したほうが混乱しなくて済みます。

演算子の多重定義の実体は関数やメンバ関数です。そうなると、演算子の優先順位はどうなるのかが気になるところです。簡単なサンプルを作ってテストしてみました。

/// 10 を法とした合同算
struct Mod10
{
  unsigned int r; // 10 を法としたときの値

  // コンストラクタ
  Mod10() : r( 0 ) {}

  // 積の代入演算子の多重定義
  Mod10& operator*=( const Mod10& i )
  {
    r = ( r * i.r ) % 10;
    return( *this );
  }

  // 和の代入演算子の多重定義
  Mod10& operator+=( const Mod10& i )
  {
    r = ( r + i.r ) % 10;
    return( *this );
  }
};

// 積の演算子の多重定義
Mod10 operator*( const Mod10& i1, const Mod10& i2 )
{
  Mod10 buff( i1 );
  buff *= i2;

  return( buff );
}

// 和の演算子の多重定義
Mod10 operator+( const Mod10& i1, const Mod10& i2 )
{
  Mod10 buff( i1 );
  buff += i2;

  return( buff );
}

int main( int argc, char* argv[] )
{
  if ( argc < 3 ) return( -1 );

  unsigned int i = atoi( argv[1] );
  unsigned int j = atoi( argv[2] );

  Mod10 m1; m1.r = i;
  Mod10 m2; m2.r = j;

  Mod10 ans1 = m2 * m1 + m1;
  Mod10 ans2 = m1 + m1 * m2;
  Mod10 ans3 = ( m1 + m1 ) * m2;

  std::cout << ans1.r << std::endl;
  std::cout << ans2.r << std::endl;
  std::cout << ans3.r << std::endl;
}

演算子の優先順位を考慮していなければ、関数に置き換えると

Mod10 ans1 = operator*( m2, operator+( m1, m1 ) );
Mod10 ans2 = operator+( m1, operator*( m1, m2 ) );

Mod10 ans1 = operator+( operator*( m2, m1 ), m1 );
Mod10 ans2 = operator*( operator+( m1, m1 ), m2 );

となって、ans1 と ans2 の値は異なるのではないかと予想していましたが、例えば m1 = 2, m2 = 3 とすると、

8
8
2

となってちゃんと優先順位が考慮されていました。で、「プログラミング言語 C++」を調べてみたら、最初の方に「優先順位を考慮している」ときちんと記述されていました。それにしても、今まで何回か使ったことがあるのに疑問に思わなかったというのが少々情けない。。。