非クラス関数による演算子オーバーロード

演算子のオーバーロードでは、算術演算子と比較演算子の説明は不十分だと説明しました。

サンプルコードで示した方法では、同じクラスインスタンス同士の演算しかできないためです。


class TestClass
{
    int num;
public:
    explicit TestClass(int n = 0)
    {
        num = n;
    }

    //+演算子のオーバーロード
    //これが呼ばれるのは
    //左辺、右辺の両方がこのクラスのインスタンスのときのみ
    TestClass operator +(const TestClass &r) const
    {
        TestClass tc;
        tc.num = num + r.num;
        return tc;
    }
};

int main()
{
    TestClass tc1(10);
 
    //整数との演算ができない
    TestClass tc2 = 1 + tc1;
}

クラスのメンバ関数で演算子をオーバーロードする場合、引数はひとつ(演算子の右辺のみ)しか受け取れないという決まりがあるためです。

もちろんそれで充分ならば構わないのですが、せっかくなら他のデータ型とも演算できたほうが便利です。
そのためには、クラス内ではなくクラス外関数としてオーバーロードを定義する必要があります。


class TestClass
{
    int num;
public:
    explicit TestClass(int n = 0)
    {
        num = n;
    }

    int get() const { return num; }

    //複合代入演算子のオーバーロード
    //+
    TestClass &operator +=(const TestClass &r)
    {
        num += r.num;
        return *this;
    }
    TestClass &operator +=(int r)
    {
        num += r;
        return *this;
    }

    //-、*、/は省略

    //比較演算子のオーバーロード
    bool operator ==(const TestClass &r) const
    {
        return num == r.num;
    }
    bool operator !=(const TestClass &r) const
    {
        return !(*this == r);
    }
    bool operator <(const TestClass &r) const
    {
        return num < r.num;
    }
    bool operator >(const TestClass &r) const
    {
        return  r < *this;
    }
    bool operator <=(const TestClass &r) const
    {
        return !(r > *this);
    }
    bool operator >=(const TestClass &r) const
    {
        return !(r < *this);
    }
};

//算術演算子のオーバーロード
//+
const TestClass operator+(const TestClass& l, const TestClass& r)
{
    return TestClass(l) += r;
}
const TestClass operator+(const TestClass& l, int r)
{
    return TestClass(l) += r;
}
const TestClass operator+(int l, const TestClass& r)
{
    return TestClass(l) += r;
}

//-、*、/は省略

//比較演算子のオーバーロード
bool operator ==(const TestClass &l, int r)
{
    return TestClass(r) == l;
}
bool operator ==(int l, const TestClass &r)
{
    return TestClass(l) == r;
}
bool operator !=(const TestClass &l, int r)
{
    return TestClass(r) != l;
}
bool operator !=(int l, const TestClass &r)
{
    return TestClass(l) != r;
}

bool operator <(const TestClass &l , int r)
{
    return TestClass(r) > l;
}
bool operator >(const TestClass &l, int r)
{
    return TestClass(r) < l;
}
bool operator <(int l, const TestClass &r)
{
    return TestClass(l) < r;
}
bool operator >(int l, const TestClass &r)
{
    return TestClass(l) > r;
}
bool operator <=(const TestClass &l, int r)
{
    return !(TestClass(r) < l);
}
bool operator >=(const TestClass &l, int r)
{
    return !(TestClass(r) > l);
}
bool operator <=(int l, const TestClass &r)
{
    return !(TestClass(l) > r);
}
bool operator >=(int l, const TestClass &r)
{
    return !(TestClass(l) < r);
}

非常に長いですね。
半分以上クラス外に関数を定義していますが、これらはすべてTestClassのオペレーターのオーバーロードです。
上記コードは以下の演算子をオーバーロードしています。

  • +
  • +=
  • <
  • >
  • <=
  • >=

算術演算子は+(+=)だけを定義しています。
後の算術演算子はほぼ同じ記述でOKです。
(ただし、引き算などは計算順に注意)

複合代入演算子は演算子のオーバーロードで説明しているのと同じです。
算術演算子のオーバーロードに必要なので記述しています。

int型とTestClass内の演算は、全部で四パターンが考えられます。

  • 1、TestClass + TestClass
  • 2、TestClass + int
  • 3、int + TestClass
  • 4、int + int

最後の「int + int」の演算ではTestClassを作ることはできないので、作成しません。
(素直に演算結果をコンストラクタに渡すなどします)

全てを外部関数化してしまうとprivateなメンバ変数にアクセスする手段がなくなってしまいますので、引数ひとつで記述できる演算子のオーバーロードはクラス内に記述しておきます。
クラス外に定義するオーバーロード関数からは、それらのクラス内オーバーロード関数を利用してprivateメンバ変数にアクセスします。

算術系の演算子のオーバーロード

複合代入演算子

まずは複合代入演算子をオーバーロードします。
これは演算子のオーバーロードでの方法と同じで構いません。


class TestClass
{
    int num;

public:
    //+=演算子をオーバーロード
    TestClass &operator +=(const TestClass &r)
    {
        num += r.num;
        return *this;
    }
    TestClass &operator +=(int r)
    {
        num += r;
        return *this;
    }
};

算術演算子

次に、算術演算子のオーバーロードを非メンバ関数で行います。
privateメンバ変数へは先ほどオーバーロードした複合代入演算子を利用してアクセスします。


//TestClass + TestClass
const TestClass operator+(const TestClass& l, const TestClass& r)
{
    return TestClass(l) += r;
}

//TestClass + int
const TestClass operator+(const TestClass& l, int r)
{
    return TestClass(l) += r;
}

//int + TestClass
const TestClass operator+(int l, const TestClass& r)
{
    return TestClass(l) += r;
}

前述した通り、演算子のオーバーロードの組み合わせは三種類ありますので、すべて定義しておきます。

引数はconstで受け取るので、新しいインスタンスを生成して返します。

前二つはクラス内で定義した「+=」をそのまま利用しています。
最後の一つはint型から新しくインスタンスを生成し、やはりクラス内で定義した「+=」演算子を利用して計算結果を返します。

これで以下のような演算が可能になります。


TestClass tc1(10);

TestClass tc2 = tc1 + 1;
TestClass tc3 = 2 + tc2;
TestClass tc4 = tc1 + tc2;

tc1 += 10;
tc2 += tc1;

比較演算子のオーバーロード

比較演算子も、privateメンバ変数にアクセスするために必要な演算子はメンバ関数として定義しておきます。

==演算子、!=演算子

これらは演算子のオーバーロードでの説明と同じです。


class TestClass
{
    int num;
public:
    bool operator ==(const TestClass &r) const
    {
        return num == r.num;
    }
    bool operator !=(const TestClass &r) const
    {
        return !(*this == r);
    }

<、>、<=、>=演算子

これもクラス内関数の処理は演算子のオーバーロードと同じです。


class TestClass
{
    int num;
public:
    bool operator <(const TestClass &r) const
    {
        return num < r.num;
    }
    bool operator >(const TestClass &r) const
    {
        return  r < *this;
    }
    bool operator <=(const TestClass &r) const
    {
        return !(r > *this);
    }
    bool operator >=(const TestClass &r) const
    {
        return !(r < *this);
    }
};

それぞれのオーバーロードの定義方法を見てください。
最初の<演算子のみが実際にメンバ変数にアクセスしており、後は既にオーバーロードした演算子を利用して処理を実装しています。

こうすることで、比較するメンバ変数を変えるなどの修正があった場合でも、ひとつのメンバ関数を変更するだけで済み、手間がかかりません。

なお、最初の<演算子以外はクラス外に書くことも可能です。
(その場合は引数にインスタンスを二つ取り、thisの代わりにする)

クラス外関数の==演算子、!=演算子

後は算術演算子のクラス外関数でのオーバーロードと同じです。
引数パターンの異なる関数を二つずつ定義するだけです。
(一つはすでにメンバ関数として存在するため、これで計三つずつ)


//比較演算子のオーバーロード
bool operator ==(const TestClass &l, int r)
{
    return TestClass(r) == l;
}
bool operator ==(int l, const TestClass &r)
{
    return TestClass(l) == r;
}
bool operator !=(const TestClass &l, int r)
{
    return TestClass(r) != l;
}
bool operator !=(int l, const TestClass &r)
{
    return TestClass(l) != r;
}

クラス外関数の<、>、<=、>=演算子


bool operator <(const TestClass &l , int r)
{
    return TestClass(r) > l;
}
bool operator >(const TestClass &l, int r)
{
    return TestClass(r) < l;
}
bool operator <(int l, const TestClass &r)
{
    return TestClass(l) < r;
}
bool operator >(int l, const TestClass &r)
{
    return TestClass(l) > r;
}
bool operator <=(const TestClass &l, int r)
{
    return !(TestClass(r) < l);
}
bool operator >=(const TestClass &l, int r)
{
    return !(TestClass(r) > l);
}
bool operator <=(int l, const TestClass &r)
{
    return !(TestClass(l) > r);
}
bool operator >=(int l, const TestClass &r)
{
    return !(TestClass(l) < r);
}

既にオーバーロードした演算子を利用して別の演算子をオーバーロードしていますので、見た目がかなりややこしいことになっています。
しかし、既存のオーバーロード定義を再利用することで、これらの比較演算子は最初の==演算子や<演算子の処理を変えるだけですべて同じ処理になるように変更されます。