はじめに

私はC++を学び始めたばかりで、間違えていることを書くこともあります。このQiitaは私の備忘録のようなもので、私と同じ初心者がこれらをみることで少しでも役に立てたらという想いで公開するかもしれません。

編集リクエストでの誤りの訂正非常にありがたいです......

演算子のオーバーロードはいいぞ

C++にはオーバーロードできる演算子がたくさんあります。それらをオーバーロードすることで例えばクラスや構造体といったオブジェクトをより便利に扱うことが出来る可能性があります。
この記事では分数を表すfractionクラスを作成し、演算子のオーバーロードの幸せスパイラルを体感したいと思います。

演算子をオーバーロードする方法

演算子をオーバーロードする方法はとても簡単です。ここではメンバ関数としてオーバーロードする方法を示します。構文はこうです。type operator operator-symbol ( parameter-list )。しかし、キャスト演算子だけはoperator type()となり、ほかとは異なります。
例えば、class hogeの代入演算子とキャスト演算子をオーバーロードする時はこのように書きます。

#include <cstdint>
class hoge{
    hoge & operator=(const hoge & src) { /* 実装 */ }
    operator std::int64_t() const { return 0; }
};

演算子たちの紹介

C++でオーバーロードできる演算子たちの紹介です。とても多いのでほかの方のQiitaを見てください。
C++演算子オーバーロード大全

とても多いです。この記事では全てを紹介することはありません。私も含めた初心者が演算子をオーバーロードすると幸せになれるということを分かってもらうことが目的です。

fractionクラス

先ほどfractionを作って演算子オーバーロードの幸せスパイラルを云々といったのでおとなしくfractionクラスを書きます。

fraction.h
#include <cstdint>
#include <cstdlib>
// 最大公約数を求める(分数を約分するときに使う)
inline std::int64_t gcd(long long a, long long b) {
    long long r = a % b;
    if(r) return gcd(b, r);
    return b;
}

// 分数クラス
class fraction {
    std::int64_t mDenominator;  // 分母
    std::int64_t mNumerator;    // 分子
public:
    constexpr fraction(std::int64_t denominator, std::int64_t molecule) : mDenominator(denominator), mNumerator(molecule) {}

    constexpr bool isMinus() const noexcept {
        return (mDenominator ^ mNumerator) >> 63 & 1;
    }
    void reduce() {
        auto gcf = gcd(std::abs(mNumerator), std::abs(mDenominator));
        mDenominator /= gcf;
        mNumerator /= gcf;
        if (isMinus()) {
            mDenominator = abs(mDenominator);
            mNumerator = -abs(mNumerator);
        }
    }
    // 分母、分子の順
    void set(std::int64_t deno, std::int64_t mole) noexcept {
        mDenominator = deno;
        mNumerator = mole;
    }
    constexpr double asdouble() const {
        return mNumerator / (double)mDenominator;
    }
    void get(std::int64_t & deno, std::int64_t & mole) const noexcept {
        deno = mDenominator; mole = mNumerator;
    }
    constexpr fraction() : mDenominator(1), mNumerator(0) {}
    //~fraction() = default;
};

こういうクラスも別に悪いことはないですが、演算子をオーバーロードするともっと幸せになれます。
例えば、asdouble()メソッドはキャスト演算子をオーバーロードすることによってより便利な形に生まれ変わります。

class fraction{
// 前略
    // double asdouble() const { return mNumerator / (double)mDenominator; }
    operator double() const { return mNumerator / (double)mDenominator; }
// 後略
};

このように書くことが出来ればdoubleを引数として受け取る関数などにfractionクラスのデータを渡す時、hoge(Fraction.asdouble());という関数呼び出しはhoge(Fraction);と書くことが出来るようになります。とても便利だと思わないならばきっと貴方は人間ではないでしょう。
この調子で比較演算子や等値演算子などもオーバーロードしていけば、vectorコンテナに入ったfractionクラスもalgorithmヘッダに入っている関数で操作できます。ソートや探索のために専用のラムダ式を書く必要はありません。

ちなみに

とても便利なのでここで紹介しますが、関数呼び出し演算子(())をオーバーロードするとstd::functionで保持することが出来る関数オブジェクトになります。
標準ライブラリではrandomヘッダのmersenne_twister_engineをはじめとする全ての乱数生成器で関数呼び出し演算子がオーバーロードされています。
そのため関数オブジェクトの配列をつくり、状況によって処理を変えるということも非常に簡単にできます。

#include <array>
#include <iostream>
#include <functional>
#include <random>
#include <cstdint>


int main() {
    std::size_t t;
    std::array<std::function<std::uint_fast32_t()>, 3> randNumGen{ std::minstd_rand{}, std::mt19937{}, std::ranlux24_base{} };
    std::cout << "choose random number generator" << std::endl << "0:linear_congruential" << std::endl << "1:mt19937" << std::endl << "2:subtract_with_carry" << std::endl;
    std::cin >> t;
    t %= 3;
    for (int i = 0; i < 10; i++) std::cout << randNumGen.at(t)() << std::endl;
    return 0;
}

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.