@uents blog

Code wins arguments.

ロベールのC++入門講座でC++を初歩から入門する(5日目)

11章 もっと高く

静的メンバ

静的メンバきた。だいたいJavaと同じ話。
静的メンバ変数は、クラス変数と同じ役割が担える。派生クラスへも上位クラスの実体がそのまま継承される。
静的メンバ関数は、クラスメソッドと同じ役割が担える。もちろん静的メンバしか扱えない。
COLUMNでシングルトンの解説。コンストラクタをprivateにして唯一の実体をクラス内で生成するらしい。今度試してみよう。
静的メンバ定数はクラス内で普遍値を定義する時に便利。

テンポラリオブジェクト

Objective-Cの「一時的なインスタンスオブジェクト」の同じやつ? 理解できてるかな…

テンポラリオブジェクトは

クラス名(実引数リスト)

で作成できる。

/**
 * @file Temporary1.cpp
 */
#include <iostream>
using namespace std;

class Hoge {
public:
	Hoge(int n) : m_n(n) {
		cout << "Hoge()    : " << m_n << endl;
	}
	Hoge(const Hoge& h) {
		m_n = h.m_n;
		cout << "Hoge(&h)  : " << m_n << endl;
	}
	virtual ~Hoge() {
		cout << "~Hoge()   : " << m_n << endl;
	}
	void operator=(const Hoge& h) {
		cout << "operator= : " << m_n << endl;
		m_n = h.m_n;
	}
	int Set(int n) {
		m_n = n;
	}
	int Get() const {
		return m_n;
	}

private:
	int m_n;
};

void Trace(int n) {
	cout << "[Trace] #" << n << endl;
}

int main() {
	Trace(0);
	Hoge hoge(1);
	Trace(1);
	hoge = Hoge(2); // temporary object
	cout << hoge.Get() << endl;
	Trace(2);
	return 0;
}

実行結果。

% ./Temporary1
>||
[Trace] #0
Hoge()    : 1
[Trace] #1
Hoge()    : 2
operator= : 1
~Hoge()   : 2
2
[Trace] #2
~Hoge()   : 2

Hoge(2)で作られたテンポラリオブジェクトは不要になった時点で即破棄されている。

テンポラリオブジェクトを返す関数も作れる。

/**
 * @file Temporary2.cpp
 */
#include <iostream>
using namespace std;

class Hoge {
	// さっきと同じ
};

void Trace(int n) {
	cout << "[Trace] #" << n << endl;
}

Hoge& One(Hoge& h) {
	h.Set(1); // 単なる参照への代入
	return h;
}

Hoge Two() {
	Hoge h(2); // temporary objectが生成される
	return h;
}

int main() {
	Trace(0);
	Hoge one(-1);
	Hoge two(-2);

	Trace(1);
	one = One(one);
	cout << one.Get() << endl;

	Trace(2);
	two = Two();
	cout << two.Get() << endl;

	Trace(3);
	return 0;
}

実行結果。

% ./Temporary2
[Trace] #0
Hoge()    : -1
Hoge()    : -2

[Trace] #1
operator= : 1
1

[Trace] #2
Hoge()    : 2
operator= : -2
~Hoge()   : 2
2

[Trace] #3
~Hoge()   : 2
~Hoge()   : 1
演算子オーバーロード

演算子オーバーロード再び。例えば、[]演算子をオーバーロードする場合は、

戻り値の型 operator[](インデックス);

とすることで、v[i]であればv.operator[i]へと置き換わる。試しに書いてみる。

/**
 * @file Vector.cpp
 */
#include <iostream>
#include <algorithm>
using namespace std;

template <typename TYPE> class Vector {
public:
	Vector(TYPE n) {
		m_size = n;
		m_array = new TYPE(n);
	}
	Vector(const Vector &v) {
		m_size = v.m_size;
		m_array = new TYPE(m_size);
		copy(v.m_array, v.m_array + m_size, m_array);
	}
	virtual ~Vector() {
		delete[] m_array;
	}
	TYPE& operator[](int index) {
		return m_array[index];
	}
	
private:
	TYPE* m_array;
	int m_size;
};

int main() {
	Vector<int> v(4);
	v[3] = 3;
	cout << "v[3] = " << v[3] << endl;
	return 0;
}

Vectorオブジェクトがconst型で生成された場合を考慮すると、const TYPE& operator[]() const {} 作る必要がある。

お、Fractionクラスが出てきた。作ってみる。

/**
 * @file Fraction.cpp
 */
#include <iostream>
using namespace std;

class Fraction {
public:
	Fraction(double numer = 0, double denom = 1) :
		m_numer(numer), m_denom(denom) {};
	Fraction(const Fraction& f) {
		m_numer = f.m_numer;
		m_denom = f.m_denom;
	}
	virtual ~Fraction() {};

#if 0
	// doubleキャストのオーバーロード
	operator double() const {
		return m_numer / m_denom;
	}
#endif

	// 代入演算
	Fraction operator=(const Fraction &f) {
		m_numer = f.m_numer;
		m_denom = f.m_denom;
	}

	// 四則演算
	Fraction operator+(const Fraction& f) {
		return Fraction(m_numer * f.m_denom + m_denom * f.m_numer,
						m_denom * f.m_denom);
	}
	Fraction operator-(const Fraction &f) {
		return this->operator+(Fraction(-1, 1) * f);
		// operator+(f * Fraction(-1, 1)) では上手く行かない...
	}
	Fraction operator*(const Fraction &f) {
		return Fraction(m_numer * f.m_numer, m_denom * f.m_denom);
	}
	Fraction operator/(const Fraction &f) {
		return this->operator*(Fraction(f.m_denom, f.m_numer));
	}

	// double値との四則演算. ここでは d <op> f の場合のみ考慮する.
	// (f <op> d の場合はdがFraction型へ暗黙的にキャストされるため問題ない)

	friend Fraction operator+(double d, const Fraction& f) {
		return Fraction(d * f.m_denom + f.m_numer, f.m_denom);
	}
	friend Fraction operator-(double d, const Fraction& f) {
		return Fraction(d * f.m_denom - f.m_numer, f.m_denom);
	}
	friend Fraction operator*(double d, const Fraction& f) {
		return Fraction(d * f.m_numer, f.m_denom);
	}
	friend Fraction operator/(double d, const Fraction& f) {
		return Fraction(d * f.m_denom, f.m_numer);
	}

	friend ostream& operator<<(ostream& out, const Fraction& f) {
		return out << f.m_numer << "/" << f.m_denom;
	}

private:
	double m_numer;
	double m_denom;
};

int main() {
	Fraction f1 = Fraction(1,2);
	Fraction f2 = Fraction(1,3);
	double d = 4.0;

	cout << "f1 = " << f1 << endl;
	cout << "f2 = " << f2 << endl;
	cout << " d = " << d << endl;
	cout << endl;

	cout << "f1 + f2 = " << (f1 + f2) << endl;
	cout << "f1 - f2 = " << (f1 - f2) << endl;
	cout << "f1 * f2 = " << (f1 * f2) << endl;
	cout << "f1 / f2 = " << (f1 / f2) << endl;
	cout << " d + f2 = " << (d + f2) << endl;
	cout << " d - f2 = " << (d - f2) << endl;
	cout << " d * f2 = " << (d * f2) << endl;
	cout << " d / f2 = " << (d / f2) << endl;

	return 0;
}

実行結果。

% ./Fraction
f1 = 1/2
f2 = 1/3
 d = 4

f1 + f2 = 5/6
f1 - f2 = 1/6
f1 * f2 = 1/6
f1 / f2 = 3/2
 d + f2 = 13/3
 d - f2 = 11/3
 d * f2 = 4/3
 d / f2 = 12/1

動くには動いたけどfriend指定がよく理解できてない。friend指定を外すと、

g++     Fraction.cpp   -o Fraction
Fraction.cpp:45: error: 'Fraction Fraction::operator+(double, const Fraction&)' must take either zero or one argument
FractionFraction.cpp:48: error: 'Fraction Fraction::operator-(double, const Fraction&)' must take either zero or one argument
Fraction.cpp:51: error: 'Fraction Fraction::operator*(double, const Fraction&)' must take either zero or one argument
Fraction.cpp:54: error: 'Fraction Fraction::operator/(double, const Fraction&)' must take exactly one argument
Fraction.cpp:58: error: 'std::ostream& Fraction::operator<<(std::ostream&, const Fraction&)' must take exactly one argument

のようなコンパイルエラーが出るが。。。


今日はここまで。中途半端だけど>< 次回は「11-12節 暗黙の了解」から。

追記

Fractionのoperator+(double, Fraction&)等をfriend関数にしなければならない理由がわかりました。

  • d fの場合は左項(最も左にある引数)dの型がFractionではないため、メンバ関数にできない
  • そこでdも引数に取り込んでしまう。その場合、この関数は非メンバ関数となる
  • 非メンバ関数の場合、Fractionのprivateメンバに直接アクセスするにはfriend指定が必要

なるほど。なおfriend関数は必ず非メンバ関数とのこと。
なので、上記の例ではfriend関数もpublic指定子を付けているが、メンバ関数ではないので、public/private指定子を付ける必要ない。さらに、メンバ関数ではないので、サブクラスへも継承されない。