演算子のカスタム定義
ユーザー定義型に対して、演算子の動作を再定義する機能が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;
}
ストリーム演算子の統合
coutとcinをユーザー定義型に対応させるには、フレンド関数としてグローバルに定義します:
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; }
};