オペレータオーバーロード
+, -, ==, <<などを自分のクラスで定義。自然な記法。
オペレータオーバーロードとは
オペレータオーバーロードは、 既存の演算子を自分のクラスに対して再定義することです。
これにより、カスタムクラスでも標準型のような自然な記法を使えます。
比較演算子のオーバーロード
== や != などの比較演算子を定義します。
#include <iostream>
#include <string>
class Person {
private:
std::string name;
int age;
public:
Person(const std::string& n, int a) : name(n), age(a) {}
// operator== のオーバーロード
bool operator==(const Person& other) const {
return name == other.name && age == other.age;
}
// operator!= のオーバーロード
bool operator!=(const Person& other) const {
return !(*this == other); // operator== を利用
}
// operator< のオーバーロード(ソート用)
bool operator<(const Person& other) const {
if (age != other.age) return age < other.age;
return name < other.name;
}
void print() const {
std::cout << name << " (" << age << "歳)" << std::endl;
}
};
int main() {
Person p1("太郎", 25);
Person p2("太郎", 25);
Person p3("花子", 23);
if (p1 == p2) {
std::cout << "p1 と p2 は同じ人" << std::endl;
}
if (p1 != p3) {
std::cout << "p1 と p3 は異なる人" << std::endl;
}
if (p3 < p1) {
std::cout << "p3 は p1 より年下" << std::endl;
}
return 0;
}
p1 と p2 は同じ人
p1 と p3 は異なる人
p3 は p1 より年下
算術演算子のオーバーロード
+, -, *, / などの算術演算子も定義できます。
#include <iostream>
#include <cmath>
class Vector2D {
private:
double x, y;
public:
Vector2D(double x_ = 0, double y_ = 0) : x(x_), y(y_) {}
// operator+ のオーバーロード
Vector2D operator+(const Vector2D& other) const {
return Vector2D(x + other.x, y + other.y);
}
// operator- のオーバーロード
Vector2D operator-(const Vector2D& other) const {
return Vector2D(x - other.x, y - other.y);
}
// operator* (スカラー倍)
Vector2D operator*(double scalar) const {
return Vector2D(x * scalar, y * scalar);
}
// スカラー倍の逆順 (scalar * vector) 用
friend Vector2D operator*(double scalar, const Vector2D& v) {
return v * scalar;
}
// 内積
double operator*(const Vector2D& other) const {
return x * other.x + y * other.y;
}
// ノルム(大きさ)
double norm() const {
return std::sqrt(x * x + y * y);
}
void print() const {
std::cout << "(" << x << ", " << y << ")" << std::endl;
}
};
int main() {
Vector2D v1(3, 4);
Vector2D v2(1, 2);
Vector2D sum = v1 + v2;
std::cout << "v1 + v2 = ";
sum.print();
Vector2D diff = v1 - v2;
std::cout << "v1 - v2 = ";
diff.print();
Vector2D scaled = v1 * 2;
std::cout << "v1 * 2 = ";
scaled.print();
double dotProduct = v1 * v2;
std::cout << "v1・v2 (内積) = " << dotProduct << std::endl;
std::cout << "|v1| (ノルム) = " << v1.norm() << std::endl;
return 0;
}
v1 + v2 = (4, 6)
v1 - v2 = (2, 2)
v1 * 2 = (6, 8)
v1・v2 (内積) = 11
|v1| (ノルム) = 5
代入演算子のオーバーロード
operator= は代入を制御します。 自己代入チェックと異なる演算子(+=など)の実装も重要です。
#include <iostream>
class Counter {
private:
int value;
public:
Counter(int v = 0) : value(v) {}
// operator= (代入演算子)
Counter& operator=(const Counter& other) {
if (this != &other) {
value = other.value;
}
std::cout << "operator= 呼び出し" << std::endl;
return *this;
}
// operator+= (加算代入)
Counter& operator+=(const Counter& other) {
value += other.value;
return *this;
}
// operator++ (前置インクリメント)
Counter& operator++() {
++value;
return *this;
}
// operator++ (後置インクリメント)
Counter operator++(int) { // int パラメータで区別
Counter temp(value);
++value;
return temp;
}
int getValue() const { return value; }
void print() const {
std::cout << "value = " << value << std::endl;
}
};
int main() {
Counter c1(5);
Counter c2(3);
c1 = c2; // operator= 呼び出し
c1.print();
c1 += c2; // operator+=
c1.print();
Counter c3 = c1; // operator= 呼び出し
c3.print();
++c1; // 前置インクリメント
c1.print();
Counter c4 = c1++; // 後置インクリメント(一時オブジェクト返却)
std::cout << "c4 = " << c4.getValue() << ", c1 = " << c1.getValue() << std::endl;
return 0;
}
operator= 呼び出し
value = 3
value = 6
operator= 呼び出し
value = 6
value = 7
c4 = 7, c1 = 8
ストリーム出力のオーバーロード(<<)
operator<< をオーバーロードで、 カスタム型を cout で出力できます。
通常、friend 関数として実装されます。
#include <iostream>
#include <string>
class Student {
private:
std::string name;
int grade;
double gpa;
public:
Student(const std::string& n, int g, double gp)
: name(n), grade(g), gpa(gp) {}
// friend 関数として operator<< を宣言
friend std::ostream& operator<<(std::ostream& os, const Student& s);
};
// friend 関数の定義
std::ostream& operator<<(std::ostream& os, const Student& s) {
os << "生徒: " << s.name
<< " | 学年: " << s.grade
<< " | GPA: " << s.gpa;
return os;
}
int main() {
Student s1("太郎", 2, 3.8);
Student s2("花子", 1, 3.9);
std::cout << s1 << std::endl;
std::cout << s2 << std::endl;
return 0;
}
生徒: 太郎 | 学年: 2 | GPA: 3.8
生徒: 花子 | 学年: 1 | GPA: 3.9
配列アクセス演算子のオーバーロード([])
operator[] で配列のような直感的アクセスを実現します。
#include <iostream>
#include <vector>
#include <stdexcept>
class SimpleArray {
private:
std::vector<int> data;
public:
SimpleArray(int size) : data(size, 0) {}
// operator[] (範囲チェック付き)
int& operator[](int index) {
if (index < 0 || index >= static_cast<int>(data.size())) {
throw std::out_of_range("インデックス外");
}
return data[index];
}
// const 版
int operator[](int index) const {
if (index < 0 || index >= static_cast<int>(data.size())) {
throw std::out_of_range("インデックス外");
}
return data[index];
}
int size() const { return data.size(); }
};
int main() {
SimpleArray arr(5);
// 書き込み
for (int i = 0; i < arr.size(); ++i) {
arr[i] = i * 10;
}
// 読み取り
std::cout << "配列内容: ";
for (int i = 0; i < arr.size(); ++i) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
// 範囲外アクセス
try {
arr[10] = 99;
} catch (const std::out_of_range& e) {
std::cout << "エラー: " << e.what() << std::endl;
}
return 0;
}
配列内容: 0 10 20 30 40
エラー: インデックス外
型変換演算子のオーバーロード
operator T() で暗黙的な型変換を定義します。 C++11 では explicit を使って安全性を高められます。
#include <iostream>
#include <string>
class Fraction {
private:
int numerator; // 分子
int denominator; // 分母
public:
Fraction(int n, int d = 1)
: numerator(n), denominator(d) {}
// double への暗黙的変換
operator double() const {
return static_cast<double>(numerator) / denominator;
}
// 文字列への explicit 変換
explicit operator std::string() const {
return std::to_string(numerator) + "/" + std::to_string(denominator);
}
};
int main() {
Fraction f(3, 4);
// 暗黙的に double に変換
double d = f;
std::cout << "Fraction(3, 4) = " << d << std::endl;
// 暗黙的変換を使用した計算
double result = f + 0.5;
std::cout << "Fraction(3, 4) + 0.5 = " << result << std::endl;
// explicit 変換は明示的に呼び出す
std::string str = static_cast<std::string>(f);
std::cout << "文字列: " << str << std::endl;
return 0;
}
Fraction(3, 4) = 0.75
Fraction(3, 4) + 0.5 = 1.25
文字列: 3/4
よくある誤り
誤り1: operator= で *this を返さない
class Bad {
public:
void operator=(const Bad& other) { // void を返す
// ...
}
};
int main() {
Bad b1, b2, b3;
// b1 = b2 = b3; // エラー! チェーン不可
}
誤り2: 後置インクリメントで int パラメータなし
class Bad {
public:
Bad operator++() { // 前置と同じシグニチャ
// ...
}
};
誤り3: operator<< をメンバ関数с
class Bad {
public:
// メンバ関数だと左オペランドが *this になる
std::ostream& operator<<(std::ostream& os) {
return os << "...";
}
};
int main() {
Bad b;
// std::cout << b; // (*b) << std::cout の順になってしまう
// b << std::cout; // こうなる
}
オーバーロード不可能な演算子
以下の演算子はオーバーロードできません:
- :: (スコープ解決)
- . (メンバアクセス)
- .* (メンバポインタアクセス)
- ?: (三項条件演算子)
- # (プリプロセッサ)
ポイント
- 比較演算子:==, !=, <, > などを定義
- 算術演算子:+, -, *, / などを定義
- 代入演算子:operator= は *this を返す
- 前置と後置:operator++() と operator++(int)
- ストリーム演算子:<< は friend 関数
- 配列アクセス:operator[] で [] 記法をサポート
- 型変換:explicit で安全性向上(C++11)
やってみよう
練習1: 複素数クラスで +, -, ==, << をオーバーロード
練習2: Rational (有理数) クラスで算術演算を実装
練習3: String クラスで +(連結)と [](アクセス)を実装
チャレンジ: Matrix クラスで * (行列積)をオーバーロード
まとめ
- オペレータオーバーロードで自然な記法が実現できる
- メンバ関数と friend 関数の使い分けが重要
- operator= は *this を返すこと、前後置の区別、自己代入チェック必須
- 過度なオーバーロードは可読性低下につながる