演算子のオーバーロード


演算子の拡張

C++言語の強力な機能の一つとして、演算子のオーバーロードがあります
これは、関数のオーバーロードのように、演算子を拡張します

演算子のオーバーロードは、独自のクラスに対する拡張です
演算子本来の意味を失わせるようなものではありません
私達もこれまで、入出力ストリーム << , > > を使っています
これは演算子をオーバーロードしているのですが、本来のシフトの意味を失っていませんね

演算子のオーバーロードは、クラスでメンバ関数として定義します
ここで用いるのが operator キーワードです

type operator operator-symbol ( parameter-list )

type は、この演算子が返す戻り値です
operator-symbol に、オーバーロードする演算子を指定します
ただし、指定できない演算子(下記)があるので注意してください

parameter-list には、2項目のオペランドを受け取ります
左辺のオペランドは、暗黙的に this ポインタとして渡されます
演算子が受け取るオペランドの数を変更することはできません
#include<iostream>
using namespace std;

class Integer {
	int value;
public:
	int operator + (Integer obj) {
		return this->value + obj.value;
	}
	Integer(int value) { this->value = value; }
};

int main() {
	Integer obj1(10) , obj2(100);
	cout << obj1 + obj2;
	return 0;
}
整数を扱う Integer クラスを生成するとすれば
このプログラムのように、演算子のオーバーロードが大活躍します

Integer オブジェクトが格納する整数を他のオブジェクトと結合するのに
このような場合 + 演算子で行えれば、直感的でわかりやすいです
上のプログラムはまさにその処理を行っています

左辺と右辺が Integer オブジェクトの + 演算子はオーバーロードされているので有効です
その場合、Integer クラスで定義されている演算子関数 +() が呼び出され処理されます
	int operator + (Integer obj) {
		return this->value + obj.value;
	}
obj1 は、暗黙的に this ポインタとして渡されています
演算子のオーバーロードは、常にユーザー定義のクラス型などに関連付けられます
そのため、必ず左辺のオペランドはクラス型で this として関数に渡されます

そして、2項目の obj2 は、関数の引数に渡されてます
結果として、二つのオブジェクトが持つ整数を結合した int 型が返されます


一般的には、通常の演算子と同じような動作をするように工夫します
たとえば、= 演算子のオーバーロードなどでは
op1 = op2 = op3 = op4 というような動作を保証するべきです
#include<iostream>
using namespace std;

class String {
public:
	char *str;
	String operator =(char *str) {
		this->str = str;
		return *this;
	}
};

int main() {
	String str1 , str2;
	str1 = str2 = "Kitty on your lap\n";
	cout << str1.str << str2.str;
	return 0;
}
このプログラムでは、thisポインタを返すことで多重代入を保証しています
もちろん、thisポインタ以外を返しても間違いではありませんが、好ましくありません
演算子のオーバーロードは、事実上は演算子になんの関係もない処理もできます
しかし、本来の意味を失わない範囲での拡張をするべきです

さらに、演算子のオーバーロードでは、デフォルト引数は使用できません
演算子の優先順位の変更もできないので注意してください

上のプログラムを見てのとおり、右辺のオペランドの型は自由です
自分のクラス型だけでなく、通常のデータ型や他のクラス型などを指定できます


単項演算子

先ほどの例では、演算子は二つのオペランドを要求しました
しかし、中には一つのオペランドしか用いない単項演算子もあります
インクリメント演算子 ++ などが代表的ですね

単項演算子のオーバーロードであれば、引数を受け取りません
引数を受け取らないことで、単項演算子をオーバーロードすることができます

type operator ++() {
//処理;
}
ただし、インクリメントやデクリメントには少し特殊な仕様があります
これだけでは、前置きか後置きを判断することができないのです
インクリメントやデクリメントには、次のように場所によって優先順位異なっています

++op
op++

これにこだわらない場合は、++() だけで両方に対処することができます
しかし、前置きか後置きかが重要なプログラムの場合は分別できると便利です
オペランドの後に置くインクリメントやデクリメントの場合、次のようにします

type operator ++(int n)

このように、整数型を受け取る型は後置きを表します
n は型を識別するためのもので、使うことはありません(常に0)
#include<iostream>
using namespace std;

class POINT {
public:
	long x;
	long y;
	void operator ++(int n) {
		x++; y++;
	}
	POINT operator ++() {
		++x; ++y;
		return *this;
	}
} obj1 , obj2 ;

int main() {
	obj1.x = 10; obj2.y = 5;
	obj2 = ++obj1;
	obj2++;

	cout << "x = " << obj1.x << "\ty = " << obj1.y << '\n';
	cout << "x = " << obj2.x << "\ty = " << obj2.y << '\n';
	return 0;
}
これは、インクリメント演算子をオーバーロードしたプログラムです
二次元座標 x と y を管理するクラス POINT を想定します

このクラスのオブジェクトは、インクリメントをサポートしています
さらに、前置きだった場合は代入などに対応するためオブジェクトを返します
後置きの場合、インクリメントのみの目的とし何も返しません

ただし、通常は本来の機能を尊重して基本的な動作を保証し
オブジェクトを返し、後置きインクリメントの動作などをサポートするべきです
//デフォルトの後置き演算子は、値を返してから1加算します


添え字演算子

単項演算子のインクリメントなども特殊な特徴がありましたが
仕様上、他の演算子とはかなり異なるのが添え字演算子 []です

添え字演算子のオペランドは、一見するとどうなっているのか理解に苦しみます
実は、演算子のオーバーロード時には2項に別れて解釈されます
obj[n] と添え時指定した場合、演算子関数には n を右辺のオペランドとして受け取ります

添え字指定に使われたオブジェクトは、今までどおり this ポインタで渡されます
つまり、obj[n] と指定すると、operator[] (int n) が呼び出されることになります
この時の obj が this ポインタとして渡されます

右辺値のデータ型は自由ですが、添え時演算子で整数以外を指定するのは奇妙です
そのため、整数型を受け取るというのが一般的です
#include <iostream>
using namespace std;

class Array {
	int ary[2];
public:
	int & operator [](int n) { return ary[n]; }
} obj ;

int main() {
	obj[0] = 10;
	obj[1] = 100;

	cout << "obj[0] = " << obj[0] << '\n';
	cout << "obj[1] = " << obj[1] << '\n';
	return 0;
}
このプログラムは、整数型変数の2つの配列を管理するクラスを持っています
添え時演算子を用いた時、オーバーロードによって、そのクラスが持つ ary[] の参照を返します

この機能を駆使して、配列管理クラスを作るプログラマも少なくありません
動的にメモリを割り当て、添え時演算子をオーバーロードして操作を管理します
ただし、それに伴うオーバーヘッドを懸念する声もあります


operator

type operator operator-symbol ( parameter-list )

クラスのインスタンスに関連付け、演算子に別の意味を持たせます
コンパイラはオペランドの型を調べて、同一演算子のそれぞれの意味を識別します

新しい演算子の定義、優先順位やオペランド数の変更
本来の演算子の再定義などはできません
また、デフォルト引数をもつこともできませんし
プリプロセッサ シンボルもオーバーロードできません

type - 戻り値の型を指定します
operator-symbol - 演算子を指定します
parameter-list - 引数リストを指定します

オーバーロードできる演算子
+-*/%^
!=<>+=-=
^=&=|=<<>><<=
<=>=&&||++--
( )[ ]newdelete&|
~ *=/=%=>>===
!=,->->*



前のページへ戻る次のページへ