C++における演算子オーバーロードと日付クラスの実装

演算子のカスタム定義

ユーザー定義型に対して、演算子の動作を再定義する機能がC++には備わっています。たとえば、日付比較のために==をオーバーロードできます。

class Calendar {
public:
    Calendar(int y, int m, int d) : year(y), month(m), day(d) {}

    bool isEqual(const Calendar& other) const {
        return year == other.year && month == other.month && day == other.day;
    }

private:
    int year, month, day;
};

bool operator==(const Calendar& lhs, const Calendar& rhs) {
    return lhs.isEqual(rhs);
}

グローバル関数として定義すると、プライベートメンバにアクセスできません。解決策は二つ:ゲッターメソッドの追加、またはメンバー関数としての定義です。

class Calendar {
public:
    // ... コンストラクタ省略

    bool operator==(const Calendar& other) const {
        return year == other.year && month == other.month && day == other.day;
    }

private:
    int year, month, day;
};

メンバー関数版では、左辺が暗黙のthisとなり、右辺が引数となります。また、.*::sizeof?:.はオーバーロード不可です。

代入演算子の再定義

代入演算子=は、既存オブジェクト同士の値コピーに使われます。コンストラクタとは異なり、初期化済みインスタンス間での操作です。

class Calendar {
public:
    Calendar(int y = 1970, int m = 1, int d = 1) : year(y), month(m), day(d) {}
    
    Calendar& operator=(const Calendar& src) {
        if (this != &src) {
            year = src.year;
            month = src.month;
            day = src.day;
        }
        return *this;
    }

private:
    int year, month, day;
};

自己代入チェックと参照返しにより、連続代入(a = b = c)や不要なコピーを回避します。未定義時はコンパイラがビット単位コピー(浅いコピー)を生成します。

前置/後置インクリメントの区別

前置++objは更新後の値を、後置obj++は更新前の値を返す必要があります。これを区別するために、後置版にはダミーint引数を追加します。

class Calendar {
public:
    // 前置++
    Calendar& operator++() {
        addDays(1);
        return *this;
    }

    // 後置++
    Calendar operator++(int) {
        Calendar temp = *this;
        addDays(1);
        return temp;
    }

private:
    void addDays(int n) { /* 日付加算処理 */ }
};

実用的な日付クラス設計

宣言と定義を分離した完全な日付クラスを構築します。まずヘッダーファイルでインターフェースを定義:

class Date {
public:
    Date(int y = 1900, int m = 1, int d = 1);
    Date(const Date& other);
    ~Date();
    Date& operator=(const Date& other);

    // 比較演算子
    bool operator>(const Date& other) const;
    bool operator==(const Date& other) const;
    bool operator>=(const Date& other) const;
    bool operator<(const Date& other) const;
    bool operator<=(const Date& other) const;
    bool operator!=(const Date& other) const;

    // 算術演算子
    Date& operator+=(int days);
    Date operator+(int days) const;
    Date& operator-=(int days);
    Date operator-(int days) const;
    int operator-(const Date& other) const; // 日数差

    // インクリメント/デクリメント
    Date& operator++();
    Date operator++(int);
    Date& operator--();
    Date operator--(int);

private:
    int year, month, day;
    static int getDaysInMonth(int y, int m);
};

実装ファイルでは、比較演算子を最小限実装し、残りはそれらを再利用します:

bool Date::operator>(const Date& other) const {
    if (year != other.year) return year > other.year;
    if (month != other.month) return month > other.month;
    return day > other.day;
}

bool Date::operator==(const Date& other) const {
    return year == other.year && month == other.month && day == other.day;
}

bool Date::operator>=(const Date& other) const {
    return *this > other || *this == other;
}

日付計算の核となるヘルパー関数:

int Date::getDaysInMonth(int y, int m) {
    static const int days[] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
    if (m == 2 && ((y % 4 == 0 && y % 100 != 0) || y % 400 == 0))
        return 29;
    return days[m];
}

加算・減算の実装:

Date& Date::operator+=(int days) {
    while (days > 0) {
        int maxDays = getDaysInMonth(year, month);
        if (day + days <= maxDays) {
            day += days;
            break;
        }
        days -= (maxDays - day + 1);
        day = 1;
        if (++month > 12) {
            month = 1;
            ++year;
        }
    }
    return *this;
}

ストリーム演算子の統合

coutcinをユーザー定義型に対応させるには、フレンド関数としてグローバルに定義します:

class Date {
    // ... 他のメンバ
    friend std::ostream& operator<<(std::ostream& os, const Date& d);
    friend std::istream& operator>>(std::istream& is, Date& d);
};

std::ostream& operator<<(std::ostream& os, const Date& d) {
    return os << d.year << "年" << d.month << "月" << d.day << "日";
}

std::istream& operator>>(std::istream& is, Date& d) {
    is >> d.year >> d.month >> d.day;
    // 入力検証を追加可能
    return is;
}

constメンバ関数

constオブジェクトから呼び出せる関数には、末尾にconst修飾子が必要です。これによりthisポインタがconst Date*型になります。

class Date {
public:
    void display() const {
        // year, month, dayを読み取り専用でアクセス
    }
};

アドレス演算子の再定義

取地址演算子&もオーバーロード可能ですが、通常はデフォルト実装で十分です:

class Date {
public:
    Date* operator&() { return this; }
    const Date* operator&() const { return this; }
};

タグ: C++ 演算子オーバーロード 日付クラス constメンバ ストリーム演算子

7月27日 20:09 投稿