C++で独自のクラスを設計している際、
a + b のように直感的に書きたい」「自作の座標クラスを
cout << point で直接出力したい」・・・と感じたことはありませんか。
これを実現するのがC++の演算子オーバーロードです。
演算子オーバーロードを使いこなすと、自作の型を int や double といった基本データ型と同じような感覚で操作できるようになり、コードの可読性が劇的に向上します。
しかし、C++の演算子(operator)の世界には、書き方のルールや、継承・メモリ管理に関わる注意点など、正しく理解しておかないとバグの原因になる要素も多く含まれています。
この記事では、演算子オーバーロードの基本的な書き方から、実務で頻出する代入演算子、ストリーム出力演算子のカスタマイズ、そしてオーバーロード可能な演算子の一覧までを網羅的に解説します。
演算子オーバーロードとは?そのメリットと基本的な考え方
演算子オーバーロードとは、C++に標準で備わっている演算子(+, -, *, /, ==, << など)を、自作したクラスや構造体に対しても使えるように「再定義」する機能のことです。
通常、自作クラス同士を足し合わせるには a.add(b) のような関数を呼び出す必要があります。
しかし、演算子オーバーロードを定義すれば a + b と記述できるようになります。
このメリットは単にタイピング量が減ることだけではありません。
数式に近い直感的な記述ができるようになることで、ロジックのミスを防ぎ、第三者が読んでも意図が伝わりやすい洗練されたコードになるのです。
演算子オーバーロードの書き方と基本ルール
演算子をオーバーロードするには、operator キーワードに続けて対象の記号を記述し、関数として定義します。
実装方法には大きく分けて「メンバ関数として定義する方法」と「非メンバ関数(フレンド関数)として定義する方法」があります。
ここでは、二次元ベクトルを扱う Vector2 クラスを例に、C++における演算子オーバーロードの具体的な書き方の基礎を見ていきましょう。
メンバ関数としての実装例(+演算子)
#include <iostream>
class Vector2 {
public:
double x, y;
Vector2(double x = 0, double y = 0) : x(x), y(y) {}
// + 演算子のオーバーロード(メンバ関数)
Vector2 operator+(const Vector2& other) const {
// 現在のオブジェクトの値と相手の値を足した新しいオブジェクトを返す
return Vector2(this->x + other.x, this->y + other.y);
}
};
int main() {
Vector2 v1(1.0, 2.0);
Vector2 v2(3.0, 4.0);
// 演算子を使って計算
Vector2 v3 = v1 + v2;
std::cout << "v3.x: " << v3.x << ", v3.y: " << v3.y << std::endl;
return 0;
}
実行結果
v3.x: 4, v3.y: 6
このソースコードが何を意味しているのかを解説します。
Vector2 クラスの中で Vector2 operator+(const Vector2& other) const という関数を定義しました。
ここでの operator+ が、足し算記号に対する新しい振る舞いを定義している部分です。
引数の other は演算子の右側に来るオブジェクトを指し、const 参照で受け取ることで無駄なコピーを避けつつ、中身を書き換えない安全性を確保しています。
関数の戻り値として、現在の x, y と相手の値を合計した新しい Vector2 オブジェクトを return しています。
これにより、v1 + v2 と書いた際に、内部では v1.operator+(v2) が呼び出され、期待通りの計算結果が得られるようになります。
また、関数の末尾にある const は、この演算自体が自分自身の値を変更しないことを保証する重要な記述です。
実務で必須!よく使われる演算子のオーバーロード例
全ての演算子をオーバーロードする必要はありませんが、一部の演算子はクラスの利便性を高めるために頻繁に利用されます。
特に代入演算子とストリーム出力演算子は、モダンなC++開発において非常に重要です。
代入演算子(operator=)の注意点
自作クラスで動的メモリ確保(new など)を行っている場合、C++の代入演算子(operator=)のオーバーロードは必須です。
デフォルトの代入では「浅いコピー」が行われ、メモリの二重解放などの致命的なバグを引き起こす可能性があるからです。
#include <iostream>
#include <algorithm>
class DynamicArray {
private:
int* data;
size_t size;
public:
DynamicArray(size_t s) : size(s) {
data = new int[size];
}
~DynamicArray() {
delete[] data;
}
// 代入演算子のオーバーロード
DynamicArray& operator=(const DynamicArray& other) {
// 自己代入チェック
if (this == &other) return *this;
// 既存メモリの解放
delete[] data;
// 新しいメモリの確保とコピー
size = other.size;
data = new int[size];
std::copy(other.data, other.data + size, data);
return *this;
}
};
代入演算子の実装において最も重要なのは、自分自身を代入しようとした場合のチェック(自己代入チェック)です。
if (this == &other) という一文を入れることで、自分自身のメモリを誤って先に削除してしまう悲劇を防いでいます。
また、戻り値を DynamicArray&(自身の参照)にしているのは、a = b = c; のような連続した代入を可能にするためです。
これは標準的なクラス設計における鉄則と言える作法となります。
ストリーム出力演算子(<<)の使い方
ストリーム出力演算子(<<)をオーバーロードすると、std::cout を使ってオブジェクトを直接表示できるようになります。
#include <iostream>
class Point {
public:
int x, y;
Point(int x, int y) : x(x), y(y) {}
// 友元(friend)関数として定義
friend std::ostream& operator<<(std::ostream& os, const Point& p) {
os << "(" << p.x << ", " << p.y << ")";
return os;
}
};
int main() {
Point p1(10, 20);
std::cout << "座標: " << p1 << std::endl;
return 0;
}
実行結果
座標: (10, 20)
このコードのポイントは、演算子をクラスのメンバではなく「独立した関数」として、かつクラスの内部データにアクセスできるように friend キーワードを使って定義している点にあります。
左辺に来るのがクラスのオブジェクトではなく std::ostream(cout の本体)であるため、メンバ関数としては実装できないという制約を、このように非メンバ関数として定義することで解決しています。
引数で受け取ったストリーム os に対して必要な文字を流し込み、最後にその os をそのまま返すことで、複数の値を << で繋げて出力する標準的な挙動を維持しています。
これによって、デバッグ時のログ出力などが飛躍的に楽になります。
C++でオーバーロード可能な演算子の一覧と制限事項
C++ではほとんどの演算子をオーバーロードできますが、一部例外や変更できないルールがあります。
オーバーロード可能な演算子
- 算術演算子:
+,-,*,/,%,+=,-=, etc. - 比較演算子:
==,!=,<,>,<=,>=,<=>(C++20以降) - 論理演算子:
!,&&,|| - ビット演算子:
&,|,^,<<,>> - その他:
[](添字),()(関数呼び出し),->(メンバアクセス),new,delete
オーバーロードできない演算子
以下の演算子は、言語の整合性を保つためにオーバーロードが禁止されています。
.(ドット演算子).*(メンバポインタアクセス)::(スコープ解決)?:(条件演算子)sizeof(サイズ取得)
また、演算子の「優先順位」や「結合規則」、「引数の数」を勝手に変えることはできません。
例えば、足し算記号 + を使って「3つの引数を取る処理」を作ることは不可能です。
既存の文法ルールを尊重した上で、特定の型に対する意味を拡張するのがこの機能の本質です。
継承と演算子オーバーロードの関係
クラスの継承が絡む場合、演算子オーバーロードの挙動には注意が必要です。
基本的に、演算子オーバーロード関数は派生クラスに継承されます。
しかし、代入演算子(operator=)だけは例外です。
代入演算子はクラスごとに暗黙的に生成される性質があるため、親クラスで定義した代入演算子が子クラスでそのまま期待通りに動くとは限りません。
子クラスに独自のメンバ変数がある場合は、子クラス側で親クラスの代入演算子を明示的に呼び出す再定義が必要になります。
C++のスキルを活かして年収を上げる方法
以上、C++での演算子オーバーロードの書き方について解説しました。
C++を扱えるエンジニアは希少価値が高いため、転職によって数十万円の年収アップはザラで、100万円以上年収が上がることも珍しくありません。
なお、転職によって年収を上げたい場合は、エンジニア専門の転職エージェントサービスを利用するのが最適です。
転職エージェントも副業エージェントも、登録・利用は完全無料なので、どんな求人や副業案件があるのか気になる方は、気軽に利用してみるとよいでしょう。
| 年収アップにこだわりたい方 (平均アップ額138万円の実績) | テックゴー |
| 未経験・経験者問わず幅広く探したい方 | ユニゾンキャリア |
| 業界に精通した担当者に相談したい方 | キッカケエージェント |
| ゲーム業界への転職を志望する方 | ファミキャリ |
| エンジニア未経験からキャリアを築く方 | イーチキャリア |


