底辺大学の院生がプログラミングや機械学習を勉強するブログ

勉強していることを雑にまとめるブログ。当然、正しさの保証は一切しない。

演算子オーバーロードとfriend関数

行列の計算を今までEigenにやらせてたんだけど、やっぱり自分で作るかーと思ったのでここ数日行列クラスを実装する作業に明け暮れてた。

こんな感じ。

// TEMPLATE CLASS Matrix
template <class Ty_>
class Matrix
{	// define my matrix class
	std::vector<std::vector<Ty_>> mat_;
public:
	explicit Matrix(const int rows = 0, const int cols = 0) :
		mat_(rows, std::vector<Ty_>(cols, 0))
	{	// construct from parameters
	}
	
	Matrix(const std::initializer_list<std::initializer_list<Ty_>> right)
	{	// construct from initializer
		for (auto iter = right.begin(); iter < right.end(); iter++)
			this->mat_.push_back(*iter);
	}

	int rows(void) const
	{	// get row size
		return this->mat_.size();
	}

	// ......
}

これがすごい勉強になる。
やっぱり汎用的なライブラリを1から作るのは大変だけど、色々テクニックを学べるから、本気でC++やるなら一度はやってみるといいと思う。

特にfriend関数とかは普通にコーディングしてるだけではなかなか出会えないけど、ここに来て多用せざるを得なくなり、改めて勉強することになった。
というわけで今日は演算子オーバーロードとfriend関数ついて。

演算子オーバーロード

こういう算術系のクラスを作る場合、コードの半分ぐらいは演算子オーバーロードで埋め尽くされる。

例えば任意の型の変数を1つ保持し、それについての処理を記述するScalarクラスがあったとする。
これにScalar+変数の演算をオーバーロードする。

template <class Ty_>
class Scalar
{
	Ty_ var_;
public:
	Scalar(const Ty_ var) : var_(var) {}
	Ty_ operator+(const Ty_ right) const { return var_ + right; }
};

こんな感じで、object+何かという演算をしたい場合は、operator+をオーバーロードして、引数に何かの方を与えればよい。
しかし、このやり方では何か+objectという演算を定義できない。

そもそもメンバ関数による演算子オーバーロードは必ず左辺に自身のオブジェクトがくる必要があるため、メンバ変数としてこれを実装することはできない。

方法として、クラスの外に

template <class Ty_>
Ty_ operator+(const Ty_ left, Scalar<Ty_> right);

を定義するというのが最初に思い浮かぶけど、var_は非公開メンバであるため、この関数内でvar_を直接参照することができず、別途var_のゲッタ等を準備する必要がある。



というわけで察しはつくだろうけど、こういう時にfriendを使いましょうという話。

template <class Ty_>
class Scalar
{
	Ty_ var_;
public:
	Scalar(const Ty_ var) : var_(var) {}
	Ty_ operator+(const Ty_ right) const { return var_ + right; }
	friend Ty_ operator+(const Ty_ left, const Scalar<Ty_> right)
	{
		return left + right.var_;
	}
};

これでOK。

ちなみにfriendはその性質上、宣言だけクラス内でして実装はクラス外でやるのが普通だけど、
クラスとの論理的な関わりの強い演算子オーバーロードの場合、見た目にも綺麗だし、inline化もされるから実装もクラス内でやったほうが良さそう。