++C++; // 未確認飛行 C

Google
Web ufcpp.net

演算子のオーバーロード

目次

キーワード

概要

オブジェクト指向言語ではクラスを定義することで自分の思い通りの「型」を作ることが出来ます。 このような自作の型は、intdouble などの組込み型と区別するため、 ユーザー定義型と呼ばれています。 ユーザー定義型の理想は、組込み型とまったく同じように扱えることです。

ユーザー定義型をあたかも組込み型であるかのように扱えるようにするため、 C#には演算子のオーバーロードというものが用意されています。 C#の組込み型には +- などの演算子が用意されていますが、 演算子のオーバーロードを行うことで、 ユーザー定義型にも自分で演算子を定義することが出来、 組込み型と同じように扱うことができます。

このように、演算子のオーバーロードによってユーザ定義型に追加された演算子のことをユーザ定義演算子と呼びます。

演算子のオーバーロードの方法

演算子は operator キーワードを用いることで、 クラスの静的メソッドとして以下のようにして定義することが出来ます。

public static 戻り値の型 operator演算子 (引数リスト)

例えば、これまでに例としてあげてきた複素数クラスに加算演算子 + を定義したい場合、 以下のように書きます。

class Complex
{
  public static Complex operator+ (Complex z, Complex w)
  {
    return new Complex(z.Re + w.Re, z.Im + w.Im);
  }
  // 残りの部分は省略
}

演算子の定義は必ず public かつ static にする必要があります。

引数リストは、 +, -, *, / などの2項演算子なら2つ、 ++, --, !, ~ などの単項演算子なら1つの引数を指定します。

演算子をオーバーロードできるといっても、C# の文法を変えてしまうようなオーバーロードはできません。 たとえば、2項演算子である / 演算子を単項演算子としてオーバーロードすることはできません。

また、引数のうち少なくとも1つの型は演算子を定義するクラス自身である必要があります。

class Complex
{
  // ↓この2つはOK。
  public static Complex operator+ (Complex z, double w)
  {
    return new Complex(z.Re + w, z.Im);
  }
  public static Complex operator+ (double z, Complex w)
  {
    return new Complex(z + w.Re, w.Im);
  }

  // ↓エラー。引数の少なくともどちらか一方は Complex でないと駄目。
  public static Complex operator+ (double z, double w)
  {
    return new Complex(z + w, 0);
  }

  // 残りの部分は省略
}

オーバーロード可能な演算子

演算子の一覧とオーバーロード可能かどうかを以下に示します。

演算子オーバーロード可能かどうか
+, -, !, ~, ++, --, true, false これらの単項演算子はオーバーロード可能です。
+, -, *, /, %, &, |, ^, <<, >> これらの2項演算子はオーバーロード可能です。
==, !=, <, >, <=, >= これらの比較演算子はオーバーロード可能です。
&&, || これらの条件 AND/OR 演算子は直接オーバーロードすることは出来ませんが、 &, |, true, false をオーバーロードすることで利用可能になります。
[] 配列の添字演算子はインデクサとして定義することが出来ます。 詳しくは 「インデクサ」 で説明します。
キャスト キャストは型変換演算子として定義することが出来ます。
+=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>= これらの代入演算子は直接オーバーロードすることは出来ませんが、 対応する2項演算子をオーバーロードすることで利用可能になります。
=, ., ?:, ->, new, is, sizeof, typeof これらの演算子はオーバーロード出来ません。

+ などの演算子は特に説明は必要ないと思います。 ここでは、説明の必要になりそうな演算子のみをとりあげます。

true, false 演算子が定義された型のオブジェクトは ifwhile, for, ?: などで条件式として利用することが出来ます。

true, false 演算子のどちらか一方を定義する場合、もう一方も定義する必要があります。 また、true, false 演算子の戻り値の型は bool でなければなりません。

class Bool
{
  int i;
  public Bool(int i){this.i = i;}
  public static bool operator true(Bool b){return b.i != 0;}
  public static bool operator false(Bool b){return b.i == 0;}
}

class OperatorSample
{
  public static void Main()
  {
    Bool b = new Bool(0);

    if(b) // 条件式として利用できる
      Console.Write("b == true");
    else
      Console.Write("b == false");
  }
}
b==false
インクリメント・デクリメント

インクリメント・デクリメント演算子は一度インスタンスをコピーし、 コピー後のインスタンスの値を変更し、戻り値とします。 前置き(++x)と後置き(x++)の2つの形式がありますが、 それぞれ以下のような手順で呼び出されます。

前置き

  • x を引数として ++, -- 演算子を呼び出し、その結果を x に代入する。
  • x をそのまま戻り値として返す。

後置き

  • x を別の場所に保存する。
  • x を引数として ++, -- 演算子を呼び出し、その結果を x に代入する。
  • 別の場所に保存しておいた、 x の変更前の値を戻り値として返す。
class Counter
{
  int i;
  public Counter(int i){this.i = i;}
  public static Counter operator ++(Counter c)
  {
    // c を直接書き換えては駄目。
    // インスタンスのコピーを作る。。
    Counter tmp = new Counter(c.i + 1);
    return tmp;
  }
  public override string ToString(){return this.i.ToString();}
}

class OperatorSample
{
  public static void Main()
  {
    Counter c = new Counter(0);

    Console.Write(c++ + "\n");
    //↑ Counter tmp = c; c = Counter.operator++(c); return tmp;
    Console.Write(c   + "\n");
    Console.Write(++c + "\n");
    //↑ c = Counter.operator++(c); return c;
    Console.Write(c   + "\n");
  }
}
0
1
2
2
条件 AND/OR 演算子

&&, || 演算子は直接オーバーロードすることは出来ませんが、 &, | 演算子および true, false 演算子をオーバーロードすることで利用可能になります。

T 型の変数 x, y に対して、 x && yT.operator false(x) ? x : T.operator &(x, y) として評価されます。 すなわち、xfalse として評価された場合、y は評価されません。

同様に、 x || yT.operator true(x) ? x : T.operator |(x, y) として評価されます。

class Bool
{
  int i;
  public Bool(int i){this.i = i==0 ? 0 : 1;}
  public static bool operator true(Bool b)
  {
    Console.Write("  operator true called\n");
    return b.i != 0;
  }
  public static bool operator false(Bool b)
  {
    Console.Write("  operator false called\n");
    return b.i == 0;
  }
  public static Bool operator &(Bool a, Bool b)
  {
    Console.Write("  operator & called\n");
    return new Bool(a.i & b.i);
  }
  public static Bool operator |(Bool a, Bool b)
  {
    Console.Write("  operator | called\n");
    return new Bool(a.i | b.i);
  }
}

class OperatorSample
{
  public static void Main()
  {
    Bool a = new Bool(1);
    Bool b = new Bool(0);

    Bool c;
    Console.Write("a && b\n");
    c = a && b;
    Console.Write("b && a\n");
    c = b && a;
    Console.Write("a || b\n");
    c = a || b;
    Console.Write("b || a\n");
    c = b || a;
  }
}
a && b
  operator false called
  operator & called
b && a
  operator false called
a || b
  operator true called
b || a
  operator true called
  operator | called
代入演算

代入演算子は直接オーバーロードすることは出来ませんが、 対応する2項演算子をオーバーロードすることで利用可能になります。

例えば、+ 演算子をオーバーロードした型は、 x += y とすることで、 x = x + y と同じ結果が得られます。

型変換演算

型変換(cast)演算子は以下のようにして定義します。

public static explicitまたはimplicit operator 変換先の型 (変換元の型 引数名)
{
  // 変換コード
}

explicit を指定して型変換演算子を定義した場合、 明示的にキャストを行わなければ型変換を行いません (明示的型変換)。 一方、 implicit を指定して型変換演算子を定義した場合、 型変換が必要になった時に自動的に型変換を行います (暗黙的型変換)。

implicit を指定した場合、 意図しないところで勝手に型変換が行われてしまう可能性があるので、 出来る限り explicit を指定しましょう。

また、変換先の型と変換元の型の少なくともどちらか一方は型変換演算子を定義するクラス自身である必要があります。

using System;

class Counter
{
  int i;

  public Counter(int i){this.i=i;}

  public static explicit operator Counter (int i){return new Counter(i);}
  public static explicit operator int (Counter c){return c.i;}
  public override string ToString(){return "count="+this.i;}
}

class OperatorSample
{
  public static void Main()
  {
    Counter c = new Counter(1);
    Console.Write((int)c + "\n");
    Console.Write((Counter)2 + "\n");
  }
}
1
count=2

演習問題

問題 1

クラス問題 1Point 構造体を2次元ベクトルとみなして、 ベクトルの和・差を計算する演算子 + および - を追加せよ。

/// <summary>
/// ベクトル和
/// </summary>
/// <param name="a">点A</param>
/// <param name="b">点B</param>
/// <returns>和</returns>
public static Point operator +(Point a, Point b)

/// <summary>
/// ベクトル差
/// </summary>
/// <param name="a">点A</param>
/// <param name="b">点B</param>
/// <returns>和</returns>
public static Point operator -(Point a, Point b)

解答

Transtation into English

[お問い合わせ](q)