C++の演算子のオーバーロードについて詳しく解説

C++には、演算子のオーバーロードという機能があります。

これは、既存の演算子を再定義して、新しい意味を持たせることができます。

この記事では、C++の演算子のオーバーロードについてわかりやすく詳しく解説します。

初心者でも理解しやすいように、サンプルコードも含めて説明します。C++プログラミングを学ぶ上で重要な知識ですので、ぜひ読んでみてください。

目次

C++の演算子のオーバーロードとは

C++では、既存の演算子を再定義することができます。これを演算子のオーバーロードと呼びます。オーバーロードされた演算子は、ユーザー定義型に対しても使用できるようになります。

例えば、+演算子をオーバーロードすることで、自分で作成したクラスのインスタンス同士を足し合わせることができます。

#include<iostream>
class MyClass {
public:
    int value;
    MyClass operator+(const MyClass& other) const {
        MyClass result;
        result.value = this->value + other.value;
        return result;
    }
};

int main() {
    MyClass a, b, c;
    a.value = 1;
    b.value = 2;
    c = a + b; // オーバーロードされた+演算子が使われる
    std::cout << c.value << std::endl; // 出力: 3
}

上記の例では、MyClassクラスにoperator+関数を定義しています。

本来であればクラスの同士の足し算はできませんが、このサンプルコードでは演算子+をオーバーロードして処理を実装しているため、引数として別のMyClassインスタンスを受け取り、それらの値を足し合わせた新しいインスタンスを生成することができるようになっています。

このように、オペレーターのオーバーロードはC++プログラマーにとって非常に便利な機能です。

しかし、過剰な使用はコードの可読性や保守性に悪影響を与える可能性があるため注意が必要です。

オーバーロード可能な演算子の種類

C++では、以下の演算子がオーバーロード可能です。

  • 代入演算子 =
  • 算術演算子 +, -, *, /, %
  • 比較演算子 ==, !=, <, >, <=, <=
  • 論理演算子 !, &&, ||
  • ビット演算子 &, |, ^, ~
  • シフト演算子 << ,>>
  • 関数呼び出し演算子()
  • アドレス取得演算子&
  • 間接参照演算子*

これらの演算子をオーバーロードすることで、ユーザー定義型に対して自然な振る舞いを実現することができます。

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

メンバ関数として演算子をオーバーロードする場合は、クラス内に演算子の定義を記述します。以下は、+演算子をオーバーロードした例です。

class MyClass {
public:
    int value;

    MyClass operator+(const MyClass& other) const {
        MyClass result;
        result.value = this->value + other.value;
        return result;
    }
};

このように定義することで、MyClassクラスのインスタンス同士を+演算子で足し合わせることができます。

MyClass a, b;
a.value = 1;
b.value = 2;

MyClass c = a + b; // c.valueは3になる

オーバーロードの例

C++では、多くの演算子がオーバーロード可能です。ここでは、いくつかの代表的な演算子について、そのオーバーロード方法を紹介します。

代入演算子のオーバーロード

代入演算子は、クラス内で定義されたメンバ変数に値を代入するために使用されます。以下は、=演算子をオーバーロードした例です。

#include <iostream>

class MyClass {
public:
    int value;
    MyClass& operator=(const MyClass& other) {
        value = other.value;
        return *this;
    }
};

int main()
{
    MyClass m1;
    m1.value = 10;
    MyClass m2 = m1;

    std::cout << m2.value << std::endl; // m2.value:10
    return 0;
}
10

このように、=演算子をオーバーロードする場合は、メンバ関数として定義します。

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

比較演算子は、2つの値を比較し真偽値を返すために使用されます。以下は、==演算子をオーバーロードした例です。

#include <iostream>

class MyClass {
public:
    int value;
    bool operator==(const MyClass& other) const {
        return value == other.value;
    }
};

int main()
{
    MyClass m1,m2;
    m1.value = 10;
    m2.value = 10;

    // m1.valueとm2.valueはどちらも10なのでtrue(1)になる 
    std::cout << (m1 == m2) << std::endl; 
    return 0;
}
1

このように、比較演算子もメンバ関数として定義します。また、引数として比較対象のクラス型の参照を取ります。そして真偽値を返すように実装します。

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

加減乗除などの算術演算子もオーバーロード可能です。以下は、+演算子をオーバーロードした例です。

#include <iostream>

class MyClass {
public:
    int value;
    MyClass operator+(const MyClass& other) const {
        MyClass result;
        result.value = value + other.value;
        return result;
    }
};

int main()
{
    MyClass m1,m2;
    m1.value = 10;
    m2.value = 20;
    MyClass result = m1 + m2;


    std::cout << result.value << std::endl; // 30 
    return 0;
}
30

このように実装することでクラス同士の計算が可能になります。- * / %も同様に個別に計算処理を実装することが可能です。

ビット演算子のオーバーロード

ビット単位で行われるAND/OR/XOR/NOTなどもオペレーター・オバロード可能です。「~」(NOT)以外は二項式であり、「~」(NOT)だけ単項式である点が異なります。「~」(NOT)も含めて以下に示します。

#include <iostream>

class BitwiseClass {
public:
    unsigned int data;

    // ビット単位 AND の定義
    BitwiseClass operator&(const BitwiseClass& rhs) {
        BitwiseClass bwObj;

        bwObj.data = data & rhs.data;

        return bwObj;
    }

    // ビット単位 OR の定義
    BitwiseClass operator|(const BitwiseClass& rhs) {
        BitwiseClass bwObj;

        bwObj.data = data | rhs.data;

        return bwObj;
    }

    // ビット単位 XOR の定義
    BitwiseClass operator^(const BitwiseClass& rhs) {
        BitwiseClass bwObj;

        bwObj.data = data ^ rhs.data;

        return bwObj;
    }

    // ビット単位 NOT の定義
    void operator~() {
        data = ~data;
        return;
    }
};


int main()
{
    BitwiseClass b1,b2;
    b1.data = 0b1111;
    b2.data = 0b0101;


    std::cout << "b1 AND b2 = " << (b1 & b2).data << std::endl;
    std::cout << "b1 OR  b2 = " << (b1 | b2).data << std::endl; 
    std::cout << "b1 XOR b2 = " << (b1 ^ b2).data << std::endl; 
    return 0;
}
b1 AND b2 = 5
b1 OR  b2 = 15
b1 XOR b2 = 10

このように、本来であれば行えない& | ^などを使ったクラス同士の論理演算も行えるようになります。

関係演算子のオーバロード

大小比較や不等号なども同様に扱えます。「<」「>」「<=」「>=」「!=」「==」がそれぞれ該当します。

#include <iostream>

class RelationalOperator {
public:
    double value;

    bool operator<(RelationalOperator obj) {
        if (value < obj.value)
            return true;
        else
            return false;
    }

    bool operator>(RelationalOperator obj) {
        if (value > obj.value)
            return true;
        else
            return false;
    }

    bool operator<=(RelationalOperator obj) {
        if (value <= obj.value)
            return true;
        else
            return false;
    }

    bool operator>=(RelationalOperator obj) {
        if (value >= obj.value)
            return true;
        else
            return false;
    }

    bool operator!=(RelationalOperator obj) {
        if (value != obj.value)
            return true;
        else
            return false;
    }

    bool operator==(RelationalOperator obj) {
        if (value == obj.value)
            return true;
        else
            return false;
    }
};


int main()
{
    RelationalOperator r1,r2;
    r1.value = 20;
    r2.value= 10;

    std::cout << "r1 <  r2 = " << std::boolalpha << (r1 <  r2) << std::endl;
    std::cout << "r1 >  r2 = " << std::boolalpha << (r1 >  r2) << std::endl; 
    std::cout << "r1 <= r2 = " << std::boolalpha << (r1 <= r2) << std::endl; 
    std::cout << "r1 <= r2 = " << std::boolalpha << (r1 <= r2) << std::endl; 
    std::cout << "r1 == r2 = " << std::boolalpha << (r1 == r2) << std::endl; 
    std::cout << "r1 != r2 = " << std::boolalpha << (r1 != r2) << std::endl; 
    return 0;
}
r1 <  r2 = false
r1 >  r2 = true
r1 <= r2 = false
r1 <= r2 = false
r1 == r2 = false
r1 != r2 = true

このように、わざわざメンバ変数にアクセスして比較しなくても、クラス同士を比較するだけで判定を行えるようになります。

インクリメント・デクリメント演算子のオーバロード

前置インクリメント/デクリメントや後置インクリメント/デクリメントも扱えます。++--がそれぞれ該当します。

#include <iostream>

class IncrementDecrement {
public:
    int count = 0;

    IncrementDecrement(int c) :count(c) {}

    // 前置インクリメント ++i を定義する。
    IncrementDecrement& operator++() {
        count++;
        return *this;
    }

    // 後置インクリメント i++ を定義する。
    IncrementDecrement& operator++(int) {

        IncrementDecrement temp(count);
        count++;
        return temp;
    }

    // 前置デクリメント --i を定義する。
    IncrementDecrement& operator--() {
        count--;
        return *this;
    }

    // 後置デクリメント i-- を定義する。
    IncrementDecrement& operator--(int) {

        IncrementDecrement temp(count);
        count--;
        return temp;
    }
};

int main()
{
    IncrementDecrement data(5);

    std::cout << "data = " << data.count << std::endl;
    data++;
    std::cout << "data = " << data.count << std::endl; 
    data--;
    std::cout << "data = " << data.count << std::endl;
    return 0;
}
data = 5
data = 6
data = 5

このように定義することでクラスをインクリメント・デクリメントできるようになります。

もちろん1ずつ加算・1ずつ減算するのではなく、10ずつ加算・100ずつ原産させるといったことも可能です。

以上がC++での演算子のオーバーロードのサンプルコードです。

終わりに

以上がC++の演算子のオーバーロードについての詳しい解説でした。

演算子のオーバーロードは、プログラムをより柔軟かつ効率的にするための重要な機能ですが、過剰利用による可読性やパフォーマンスの低下、互換性の問題にも注意が必要です。

適切な場面で使い分けることで、より高度なプログラミングを実現してください。

目次