第7回 STL利用法、多重継承

多重継承

多重継承(Multiple Inheritance)は、 オブジェクト指向言語特有の機能のひとつであり、C++に備わっている機能の代表例です。 Java言語には、多重継承は機能としてありませんが、 類似の機能としてインタフェース機能で複数のインタフェースをあるクラスに実装(implement)することができるようになっています。一方、Python言語には、C++とそっくりな多重継承が可能です。 実例で多重継承を説明します。 図に示すように、 MotherクラスとFatherクラス(一般には複数の基底クラス)の両方をChildクラス(一般には派生クラス)で継承することができます。



これを使ったプログラムの例は以下のようです。
MultipleInheritance.cpp
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/*プログラム7-1 多重継承例 */
#include <iostream>
using namespace std;

class Mother {/* 母親 */
		string maidenName;/* 旧姓 */
		string firstName;/* ファーストネーム */
	public:
		string bloodType;/* 血液型 */
		Mother(string firstName, string maidenName, string bloodType):
			maidenName(maidenName),
			firstName(firstName), 
			bloodType(bloodType){ }
		void print(){
			cout << "母親の名前(旧姓):" << maidenName  << " " << firstName << endl;
		}
};
class Father {/* 父親 */
		string firstName;/* ファーストネーム */
	public:
		string bloodType;/* 血液型 */
		string familyName;/* 苗字 */
		Father(string firstName, string lastName, string bloodType):
			firstName(firstName), 
			bloodType(bloodType),
			familyName(lastName){}
		void print(){
			cout << "父親の名前:" << familyName << " " << firstName << endl;
		}
};
class Child: public Mother, public Father {/* 子供 */
	string bloodType;/* 血液型 */
	string myName;/* 名前 */
	public:
		Child(string myName, string lastName, string dadName, string momName,
			string momMaidenName, string myBlood, string dadBlood, string momBlood):
			Mother(momName, momMaidenName, momBlood),
			Father(dadName, lastName, dadBlood),
			bloodType(myBlood),
			myName(myName) {}
		void printBloodType(){
			cout << "\t 父親の血液型は" <<  Father::bloodType << "です" << endl;
			cout << "\t 母親の血液型は" <<  Mother::bloodType << "です" << endl;
			cout << "\t 自分の血液型は" << bloodType << "です" << endl;
		}
		void print(){
			 Father::print();
			 Mother::print();
			cout << "自分の名前:" << familyName << " " << myName << endl;
			cout << endl;
			printBloodType();
		}
};
int main(){
	Child son("一郎", "山田", "太郎", "花子", "鈴木", "A", "O", "AB");
	son.print();
	return 0;
}
多重継承は、プログラム7-1の31行目に示すように、 class Child : public Mother, public Fatherのように2つ以上の基底クラスをカンマで並べて記述します。 多重継承では2つの問題を解決する必要があります。 ひとつは、継承する基底クラスのインスタンスの初期化をどこでどのように行うか、 もうひとつは、2つ以上の基底クラスがあるために生じる「曖昧さ」の回避方法です。

まず、基底クラスのインスタンスは、 派生クラスのコンストラクタの初期化で基底クラスのコンストラクタを呼び出すことのみで可能です(プログラム7-1の37,38行)。 一方、「曖昧さ」に関しては、この例では、MotherクラスにもFatherクラスにも、 string bloodTypeというメンバー変数やvoid print()という名前のメンバー関数があります。 しかもいずれも派生クラスのChildクラスにもこれらのデータや関数が含まれます。 従って、単にprintを呼び出してもどのprint関数かが「曖昧」になってしまいます。 このため、クラス修飾子(名前空間名とほぼ同様な働き)をつけて参照することで解決します。 42~43行、47~48行が、これらに相当します。 実行結果は以下のようです。

$ g++ -Wall -std=c++14 -o MultipleInheritance MultipleInheritance.cpp

$ ./MultipleInheritance
父親の名前:山田 太郎
母親の名前(旧姓):鈴木 花子
自分の名前:山田 一郎

         父親の血液型はOです
         母親の血液型はABです
         自分の血液型はAです

車での多重継承例

次に以下のような車の例で、多重継承を考えてみます。



この場合、クラスの定義のあらましは、以下のようになります。

	class Car { ...};/* 自動車 */
	class DieselCar: public Car { ... }; /* ディーゼル自動車 */
	class GasCar : public Car { ... };/* ガソリン自動車 */
	class EcoCar : public Car { ... };/* エコカー */
	class HVcar : public EcoCar { ... };/* ハイブリッド自動車 */
	class PHVcar : public EcoCar { ... };/* プラグインハイブリッド自動車 */
	class EVcar : public EcoCar { ... };/* 電気自動車 */
	class FCVcar : public EcoCar { ... };/* 燃料電池自動車 */
	class INSIGHT : public HVcar { ... };/* インサイト */
	class VHVcar : public HVcar, public PHVcar { ... };/* 各種ハイブリッド自動車 */
	class VEVcar : public PHVcar, public EVcar { ... };/* 各種電気自動車 */
	class PRIUS: public VHVcar { ... };/* プリウス */
	class ACCORD: public VHVcar { ... };/* アコード */
	class MiEV : public EVcar { ... };/* MiEV */
	class MIRAI : public FCVcar { ... };/* MIRAI */

一般に、クラスの継承関係が自然に書けるのは、 「is-a関係」がある場合です。 日本語でいうと、「YはXの一種(ひとつ)である」という関係が成り立つ場合、 Xが基底クラスで、Yが派生クラスとして自然に表現できます。

多重継承におけるダイアモンド問題

多重継承で、継承元の基底クラスに同じ名前がある場合、 クラス名を名前空間として(qualified name(限定子)として)名前の解決ができました。 しかし、上の車の例では、たとえば「エコカー」を「PHVカー」と「EVカー」が継承し、 この2つを「各種EVカー」が多重継承しています。ここで、この4つのクラスの形を見ると、 ダイアモンド形になっています。 このような場合、大きな問題が生じる場合があります。 具体的には、先祖にあたる「エコカー」クラスにあるメンバー関数を、 「各種EVカー」で(そこではオーバーライドしないとして)参照したい場合、 「エコカー」クラスを継承する「PHVカー」クラスのオーバーライド関数と、 同じ「エコカー」クラスを継承する「EVカー」クラスのオーバーライド関数が、 全く違う風に実装されている場合、 「各種EVカー」クラスからは、 どちらのメンバー関数を呼び出せばいいか決定できないという曖昧さがあるという問題です。

ダイヤモンド継承で起こる問題として、基底クラスを継承する派生クラスの片方が消滅したにもかかわらず、もう片方が消滅しないと、メモリリークを発生してしまいます。 そこで、 デストラクタを仮想関数にすることで、この問題を緩和できることが知られています。 一般的に、 クラスの継承を行う場合、基底クラスに派生クラスのインスタンスを代入することができますが、 代入後の基底クラス側のデストラクタをdelete等の命令で呼び出したとき、 派生クラスのデストラクタが呼び出されないと、メモリリークを起こす危険があります。 一方、基底クラスのデストラクタを仮想関数にしておくと、派生クラスのインスタンスを 基底クラスのポインタ等に代入した後、基底クラス側でdeleteを呼び出すと、 多重継承されていても「芋づる式に」派生クラスのデストラクタがちゃんと呼び出されてメモリリークの危険が減ります。 継承される可能性のあるすべてのクラスにおいて、デストラクタを仮想関数にしておく、 というのがC++の「コツ」のひとつである、と言われています。
Car.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/*プログラム7-2 多重継承例におけるCarクラス  */
#ifndef _CAR
#define _CAR
#include <iostream>
using namespace std;
#include <string>
#include <iomanip>

class Car {
	public:
		Car(){}/* コンストラクタ */
		Car(string plateNumber, string maker, int year, 
			double distance, double price):	/*コンストラクタ */
			plateNumber(plateNumber), maker(maker),
			year(year), distance(distance), price(price){ 
			energy = gas;
		}
		Car(const Car &car);/* コピーコンストラクタ */
		virtual ~Car(){	
			cout << "基底関数Carの\"" << plateNumber << 
				"\"に対するデストラクタが呼ばれました" << endl;
		}/* 仮想デストラクタ */
		void print();/* 定義されている属性(メンバー変数)をプリント */
		double calculateValue();/* 現在の価格を計算 */
		void setPrice( double price );/* 販売価格の設定 */
		double getPrice(); /* 販売価格の入手 */
	private:
		string plateNumber; /* プレートナンバー */
		string maker; /* メーカー */
		int year; /* 製造年 */
		double distance;/* 走行距離 */
		double price; /* 販売価格 */
		double kph; /* 時速(KPH) kilometer per hour */
		string type; /* 車種 */
		double horsePower; /* 馬力 */
		enum { gas, hybrid, electric } energy;/* 燃料 */
		string color; /* 色 */
		double length; /* サイズ:前後 */
		double width; /* サイズ:左右 */
		double height; /* サイズ:高さ */
};

void Car::print(){/* プリント関数 */
	cout << "-------------------------------------" << endl;
	cout << "メーカー: " << maker << endl;
	cout << "プレート番号: " << plateNumber << endl;
	cout << "製造年: " << year << "年" << endl;
	cout << "走行距離:" << distance << " km" << endl;
	cout << "購入価格 = \\" << 
		fixed << setprecision(1) << setw(8) << price << endl;
	cout << "現在の価値 = \\" << 
		fixed << setprecision(1) << setw(8) << calculateValue() << endl;
	cout << endl;
}

/* 価格見積もり関数 (実際の中古車価格モデルとは異なる想像モデル)*/
const double THIS_YEAR =2015.0;
const double YEAR_COEF = 20.0; /* 製造年から見た中古度合係数 */
const double MILEAGE_COEF = 0.085; /* 走行距離での減額係数 */
const double DOWN_COEF =0.55; /* 全体係数 */
double Car::calculateValue(){/* 中古車見積もり例 */
	double t = ::YEAR_COEF * ( year - ::THIS_YEAR ) ;
	double d = ::MILEAGE_COEF * distance;
	double estimate = ::DOWN_COEF * (price  +  t * d);
	return estimate;
}

double Car::getPrice(){ return price;}/* 初期価格を返す関数 */
void Car:: setPrice(double price){/* 初期価格を設定する関数 */ this->price = price; }
#endif
EcoCar.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*プログラム7-3 多重継承例におけるEcoCarクラス */
#ifndef _ECOCAR
#define _ECOCAR
#include "Car.h"

class EcoCar : public Car {/* Carクラスを継承 */
 public:
	EcoCar(){}/* コンストラクタ */
	EcoCar(string plateNumber, string maker, int year, 
		double distance, double price):/*コンストラクタ */
		Car(plateNumber, maker, year, distance, price){
		cout << "EcoCar: plateNumber = " << plateNumber << " maker = " <<
			maker << endl; 
	}
	virtual ~EcoCar(){cout << "EcoCarのデストラクタが呼ばれました" << endl;}/* 仮想デストラクタ */
	void print(){ Car::print(); }/* 基底クラスのprintを呼び出す */
 private:
	double ecoRate; /* エコ割合 */
};
#endif
PHVcar.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/*プログラム7-4 多重継承例におけるPHVcarクラス (プラグインハイブリッドカー) */
#ifndef _PHVCAR
#define _PHVCAR
#include "EcoCar.h"

class PHVcar : public EcoCar {/* プラグインハイブリッドカー:直接充電できるハイブリッド */
  public:
	PHVcar(){}/* コンストラクタ */
	PHVcar(string plateNumber, string maker, int year, 
		double distance, double price):/*コンストラクタ(一部のメンバー変数のみ) */
		EcoCar(plateNumber, maker, year, distance, price){}
	virtual ~PHVcar(){cout << "PHVcarのデストラクタが呼ばれました" << endl;}/* 仮想デストラクタ */
	void setChargeTime(double chargeTime){ this->chargeTime = chargeTime; }/* 充電時間の設定 */
	double getChargeTime(){ return chargeTime; }/* 充電時間の問い合わせ */
	void setVoltage(double voltage){ this->voltage = voltage; }/* ボルテージの設定 */
	double getVoltage(){ return voltage; }/*ボルテージの問い合わせ */
	void print(){ 
		cout << "【PHVcar: " << voltage << "ボルト】" << endl; 
		EcoCar::print();/* 基底クラスのprintメソッドを呼び出す */
	}
  private:
	double chargeTime; /* 充電時間 */
	string batteryType; /* バッテリ種類 */
	string outletType; /* 充電プラグタイプ */
	double voltage; /* ボルテージ (V) */
	double eRate; /* 電気割合 */
	double gasRate; /* ガソリン割合 */
};
#endif
EVcar.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/*プログラム7-5 多重継承例におけるEVcarクラス (電気自動車) */
#ifndef _EVCAR
#define _EVCAR
#include "EcoCar.h"

class EVcar : public EcoCar {
 public:
	EVcar(){ called = false;}/* コンストラクタ */
	EVcar(string batteryType, string outletType):/*コンストラクタ(2引数) */
		EcoCar("", "", 2015, 0.0, 0.0), batteryType(batteryType), outletType(outletType), called(false) {}
	EVcar(string plateNumber, int year, double distance, double price):	/* コンストラクタ(4引数) */
		EcoCar(plateNumber, "三菱", year, distance, price), called(true) {
			cout << "EVcar: plateNumber = " << plateNumber << endl; 
	}
	virtual ~EVcar(){cout << "EVcarのデストラクタが呼ばれました" << endl;}/* 仮想デストラクタ */
	void setChargeTime(double chargeTime){ this->chargeTime = chargeTime; }/* 充電時間の設定 */
	double getChargeTime(){ return chargeTime; }/* 充電時間の問い合わせ */
	void setVoltage(double voltage){ this->voltage = voltage; }/* ボルテージの設定 */
	double getVoltage(){ return voltage; }/*ボルテージの問い合わせ */
	void print(){ 
		cout << "【EVcar: " << voltage << "ボルト】" << endl; 
		if (called) EcoCar::print();/* コンストラクタの引数に応じてprintを変える */
	}
 private:
	double chargeTime; /* 充電時間 */
	string batteryType; /* バッテリ種類 */
	string outletType; /* 充電プラグタイプ */
	double voltage; /* ボルテージ (V) */
	bool called;/* 4引数のコンストラクタが呼び出された */
};
#endif
VEVcar.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/*プログラム7-6 多重継承例におけるVEVcarクラス (Various EV Car 各種電気自動車) */
#ifndef _VEVCAR
#define _VEHVCAR
#include "PHVcar.h"
#include "EVcar.h"

class VEVcar : public PHVcar, public EVcar { /* 多重継承 */
  public:
	VEVcar(){ called4 = false;}/* コンストラクタ */
	VEVcar(string plateNumber, string maker, int year, 
		double distance, double price)/*コンストラクタ(5引数) */
		:PHVcar(plateNumber, maker, year, distance, price), called4(false){}
	VEVcar(string plateNumber, int year, 
		double distance, double price)/*コンストラクタ(4引数) */
		:EVcar(plateNumber, year, distance, price), called4(true) {
			cout << "VEVcar: 4引数 " << plateNumber << endl; 
	}
	virtual ~VEVcar(){
		cout << "VEVcarのデストラクタが呼ばれました" << endl;
	}/* 仮想デストラクタ */
	void setVoltage(double voltage){ 
		if (called4)
			EVcar::setVoltage(voltage); 
		else 
			PHVcar::setVoltage(voltage);
	}/* ボルテージの設定 */
	void print(){
		if (called4)
			EVcar:: print();
		else 
			PHVcar::print();
	}
	private:
		bool called4; /* 4引数がコールされたかどうか */
};
#endif
CarMain.cpp
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*プログラム7-7 多重継承例におけるmain関数 */
#include "VEVcar.h"

int main(){
	/* 各種電気自動車のインスタンス作成1: 5引数 (PHPcarに代入) */
	PHVcar *car1 = new VEVcar("豊橋ね 93-38", "トヨタ", 2013, 52000, 2000000);
	car1->setVoltage(120.0);
	car1->print();
	delete car1;
	cout << endl << "******************************************" << endl;

	/* 各種電気自動車のインスタンス作成2: 4引数  (EVcarに代入) */
	EVcar *car2 = new VEVcar("豊橋な 07-10", 2014, 12000, 3500000);
	car2->setVoltage(240.0);
	car2->print();
	delete car2;
	cout << endl << "******************************************" << endl;

	/* 各種電気自動車のインスタンス作成3: 4引数  (VEVcarそのままだと基底はどっち?)*/
	VEVcar *car3 = new VEVcar("豊橋へ 00-01", 2015, 10000, 4000000);
	car3->setVoltage(100.0);
	car3->print();
	delete car3;
	return 0;
}
このmain関数のあるプログラムでは、 各種電気自動車クラスであるVEVcarクラスのオブジェクトを生成し、 6行目ではこれをPHVcarクラスのポインタ変数に、また、 13行目ではこれをEVcarクラスのポインタ変数に代入しています。 これらは、元来、それぞれの基底クラスのオブジェクトを生成しても同様です。

一方、20行目では、基底クラスのポインタ変数に代入することなく、 そのまま派生クラスのVEVcarクラスのポインタ変数に代入しています。 ところが21行目から22行目のメンバー関数は基底クラスのものなので、 多重継承しているVEVcarクラスにとって、EVcarクラスのメンバ関数なのか、 あるいはPHVcarクラスのメンバ関数なのか、通常は曖昧性のため解決できません。 ここでは、コンストラクタの引数を継承している基底クラスにより変更することで解決しています。 なお、6行目と13行目で、PHPcar, EVcarクラスのポインタ変数の代わりに、 それぞれの基底クラスであるEcoCarクラスのポインタ変数に代入することは、 別な曖昧性の問題が生じるため、できませんので注意してください。

これを 次に、上述のプログラム7-7を実行した結果は以下のようです。

$ make
g++ -Wall -O3 -std=c++14 -c CarMain.cpp
g++ -Wall -O3 -std=c++14 -o CarMain CarMain.o

$ ./CarMain
EcoCar: plateNumber = 豊橋ね 93-38 maker = トヨタ
【PHVcar: 120ボルト】
-------------------------------------
メーカー: トヨタ
プレート番号: 豊橋ね 93-38
製造年: 2013年
走行距離:52000 km
購入価格 = \2000000.0
現在の価値 = \1002760.0

VEVcarのデストラクタが呼ばれました
EVcarのデストラクタが呼ばれました
EcoCarのデストラクタが呼ばれました
基底関数Carの""に対するデストラクタが呼ばれました
PHVcarのデストラクタが呼ばれました
EcoCarのデストラクタが呼ばれました
基底関数Carの"豊橋ね 93-38"に対するデストラクタが呼ばれました

******************************************
EcoCar: plateNumber = 豊橋な 07-10 maker = 三菱
EVcar: plateNumber = 豊橋な 07-10
VEVcar: 4引数 豊橋な 07-10
【EVcar: 240.0ボルト】
-------------------------------------
メーカー: 三菱
プレート番号: 豊橋な 07-10
製造年: 2014年
走行距離:12000.0 km
購入価格 = \3500000.0
現在の価値 = \1913780.0

VEVcarのデストラクタが呼ばれました
EVcarのデストラクタが呼ばれました
EcoCarのデストラクタが呼ばれました
基底関数Carの"豊橋な 07-10"に対するデストラクタが呼ばれました
PHVcarのデストラクタが呼ばれました
EcoCarのデストラクタが呼ばれました
基底関数Carの""に対するデストラクタが呼ばれました

******************************************
EcoCar: plateNumber = 豊橋へ 00-01 maker = 三菱
EVcar: plateNumber = 豊橋へ 00-01
VEVcar: 4引数 豊橋へ 00-01
【EVcar: 100.0ボルト】
-------------------------------------
メーカー: 三菱
プレート番号: 豊橋へ 00-01
製造年: 2015年
走行距離:10000.0 km
購入価格 = \4000000.0
現在の価値 = \2200000.0

VEVcarのデストラクタが呼ばれました
EVcarのデストラクタが呼ばれました
EcoCarのデストラクタが呼ばれました
基底関数Carの"豊橋へ 00-01"に対するデストラクタが呼ばれました
PHVcarのデストラクタが呼ばれました
EcoCarのデストラクタが呼ばれました
基底関数Carの""に対するデストラクタが呼ばれました

デストラクタを仮想関数としたことで、派生クラス側のデストラクタがすべて呼び出されていることがわかります。

画像処理と演算子のオーバーロード

画像処理は、OpenCVのようなC++言語に適したAPIを使うことで非常に簡単に使えるようになりました。 ここでは、JPEG形式の画像を扱えるフリーソフトを利用して、簡単な画像処理の仕方を紹介します。

ツールとしては、上述のサイトからダウンロードできるjpegの バージョン9とし、そのライブラリをlibjpeg.aとして作成しておきます。

画像のポジティブ・ネガティブ変換例

以降、いつもと逆順に、main関数を含むプログラムから紹介します。 今、以下の左図のようなJPEG画像が与えられ、 これを右図のようなネガティブ(RGB各色で輝度反転)画像に変換したいとします。


これを実現するプログラムは、以下のように書けます。
JpegPNtranform.cpp
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/* プログラム7-8 画像のポジティブ・ネガティブ変換 */
#include <iostream>
using namespace std;
#include "jpegio.h"

int NEGATIVE(int x) { return(255-x); }/* 輝度反転関数 */

int main(int argc, char **argv)
{
	char *out_filename;
	/* 実行時引数から入力ファイル名を決定 */
  	if (argc < 2 || argc > 3){
		cerr << "usage : " << argv[1] << " "  << " 出力JPEGファイル名" << endl;
		exit(1);
	}
	else if (argc == 2)
		out_filename = (char *)"output.jpg";
	else  /* argc == 3 出力ファイル名が指定されていた場合 */
		out_filename = argv[2];

	/* JPEGクラスの画像オブジェクト生成(読込)*/
	JpegImage imgIn;/* 読み込むJpegImageオブジェクト */

	/* JPEGライブラリを用いて画像データ入力 */
	imgIn.readJpeg(argv[1]);/* JpegImageオブジェクトの読込み */
	/* 読み込んだ画像の画素単位のバイト数と縦横サイズを画面に表示する */
	cout << "入力画像ファイル名:" << argv[1] << endl;
	cout << "\tbyte per pixel = " << imgIn.iB() << endl;
	cout << "\timage size: " << imgIn.iW() << " x " << imgIn.iH() << endl;

	/* 出力用JPEG画像クラス */
	JpegImage imgOut(imgIn.iW(), imgIn.iH(), imgIn.iB());
	imgOut.setFunc(::NEGATIVE);/* 画像処理(色反転)関数をセット  */
	imgOut = imgIn;/* 画像処理関数を適用しながらの代入演算(オーバーロード) */

	/* JPEGファイルの書き出し */
	imgOut.writeJpeg(out_filename);
	return(0);
}

次に、上述のプログラムを実行した結果は以下のようです。

$ make
g++ -Wall -O3 -std=c++14 -c JpegPNtransform.cpp
g++ -Wall -O3 -std=c++14 -o JpegPNtransform JpegPNtransform.o libjpeg.a
$
$ make run
./JpegPNtransform  124.jpg  output.jpg
入力画像ファイル名:124.jpg
        byte per pixel = 3
        image size: 384 x 256
上述のリンク時にlibjpeg.aというライブラリをロードしていることがわかると思います。 次に、ここから逆に、コンパイルに必要なヘッダファイルjpegio.h(JpegImageクラス)の定義例を紹介します。
jpegio.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
/* プログラム7-9 JpegImageクラスを定義するヘッダーファイル*/
#ifndef _JPEGIO
#define _JPEGIO
#include <cstdio>
#include <cstdlib>
#include <csetjmp>
#include <cstring>
#include <cmath>
#include "Color.h"
using UCHAR = unsigned char;// C++11 
using IFPtr = int (*)(int);/* 整数引数をもつ整数型を返す関数へのポインタ */

class JpegImage { /* JPEGクラス */
	int image_width;		/* 入力画像の幅 */
	int image_height;		/* 入力画像の高さ */
	int image_bytepp;		/* 1画素が有するバイト数 */
	UCHAR *image_buffer;	/* 入力画像のRGBデータへのポインタ */
	IFPtr func; /* ある画像処理を行う関数へのポインタ */
 private:
	void allocateFromScratch(int w, int h, int b){
		image_buffer = new UCHAR[w * h * b];
		func = nullptr;
	}
	void write_JPEG_file (char * filename, int quality);
	void write_JPEG_file (char * filename);/* JPEGファイル書き出し */
	int  read_JPEG_file (char * filename);/* JPEGファイル読込 */
 public:
	JpegImage(){ image_buffer = nullptr; } 
	JpegImage(int w, int h, int b) : image_width(w), image_height(h), image_bytepp(b){
		allocateFromScratch(w, h, b);
	}
	Color getColor(int x, int y){ 
		int addr = x * image_bytepp + y * image_width * image_bytepp;
		/* RGB3色の色反転を繰り返す */
		int b = image_buffer[addr+0];/* 青成分 */
		int g = image_buffer[addr+1];/* 緑成分 */
		int r = image_buffer[addr+2];/* 赤成分 */
		float fr = (float) r / 255.0F;
		float fg = (float) g / 255.0F;
		float fb = (float) b / 255.0F;
		return Color(fr,fg,fb);
	}
	void setColor(int x, int y, Color c){
		int addr = x * image_bytepp + y * image_width * image_bytepp;
		int r = (int) (c.R() * 255);/* 青成分 */
		int g = (int) (c.G() * 255);/* 緑成分 */
		int b = (int) (c.B() * 255);/* 赤成分 */
		image_buffer[addr+0] = b;
		image_buffer[addr+1] = g;
		image_buffer[addr+2] = r;
	}
	int iH(){ return image_height;}/* 画像の高さ */
	int iW(){ return image_width;}/* 画像の横幅 */
	int iB(){ return image_bytepp;}/* 画像の画素あたりのバイト数 */
	int readJpeg(char *filename){/* JPEGファイルの読込:公開メソッド */
		int rc = read_JPEG_file(filename);
		func = nullptr;
		return(rc);
	}
	void writeJpeg(char *filename){/* JPEGファイルの書き出し:公開メソッド */
		write_JPEG_file(filename);
	}
	void setFunc(IFPtr f){ func = f; }/* 画像処理関数の設定 */
	void  operator = (const JpegImage &A){/* RGBデータ配列への代入オーバーロード*/
		for ( int y = 0 ; y < A.image_height ; y++ ){
			for ( int x = 0 ; x < A.image_width ; x++ ){
				/* 座標(x,y)の画素のアドレスを計算する */
				int addr = x * A.image_bytepp + y * A.image_width * A.image_bytepp;
				/* RGB3色の色反転を繰り返す */
				image_buffer[addr+0] = func(A.image_buffer[addr+0]);/* 青成分 */
				image_buffer[addr+1] = func(A.image_buffer[addr+1]);/* 緑成分 */
				image_buffer[addr+2] = func(A.image_buffer[addr+2]);/* 赤成分 */
			}
		}
	}
	void  operator |= (const JpegImage &A){/* RGB配列のビットOR代入オーバーロード */
		for ( int y = 0 ; y < A.image_height ; y++ ){
			for ( int x = 0 ; x < A.image_width ; x++ ){
				/* 座標(x,y)の画素のアドレスを計算する */
				int addr = x * A.image_bytepp + y * A.image_width * A.image_bytepp;
				/* RGB3色を足しこむ */
				image_buffer[addr+0] |= A.image_buffer[addr + 0];/* 青色成分の加算 */
				image_buffer[addr+1] |= A.image_buffer[addr + 1];/* 緑色成分の加算 */
				image_buffer[addr+2] |= A.image_buffer[addr + 2];/* 赤色成分の加算 */
			}
		}
	}
};
#endif
このプログラムから公開しているメソッドは主として、JPEGファイルを読み込む関数、 JPEGファイルを書き出す関数、代入演算子のオーバーロード、 ビットOR演算子のオーバーロードなどであることがわかります。 23-25行目にある(緑色で表現している)I/O関数は、privateなメンバー関数ですが、 これらの本体は、ライブラリlibjpeg.aの中で定義されているので詳細は気にしなくて結構です。 ただし、image_buffer変数で画像からRGB値を取得した時、 メモリ内に配置されるバイトの順番がR,G,Bの順ではなく、B,G,Rの順番になっている点に注意してください。

色に関しては、上述のプログラムで現れるimage_buffer変数はUCHAR型(unsigned char型)であり、 1バイト(8ビット)の値を持ちます。 すなわち、2の8乗で256段階の輝度を有します。 この値がR,G,Bそれぞれ存在し、すべてがゼロなら黒色、すべてが255であれば白色を表します。 また、R,G,B各色を0.0が黒で、1.0が白となるように実数で表現するColorクラスを別途、用意しておきます。 実際の画像中のR,G,B各色は1バイト、すなわち256段階の濃淡で表現できますが、 一般には、違う濃淡レベルもあっていいので、これらの値を上述のように実数値で表現するわけです。 Colorクラスの実装例は以下のようです。
Color.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/* プログラム7-10 Colorクラス */
#ifndef _COLOR
#define _COLOR

#include <iostream>
using namespace std;

class Color {
	float r,g,b; /* RGBモデル */
public:
	Color(){ r = g = b = 0.0f; }
	Color(float r, float g, float b): r(r), g(g), b(b) {}/* コンストラクタ */
	void operator = (const Color &c){/* 代入演算子オーバーロード */
		this->r = c.r;
		this->g = c.g;
		this->b = c.b;
	}
	~Color(){}/* デストラクタ */
	float R(){ return r;}/* 赤をゲット */
	float G(){ return g;}/* 緑をゲット */
	float B(){ return b;}/* 青をゲット */
	void setR(float r){ this->r = r; }/* 赤をセット */
	void setG(float g){ this->g = g; }/* 緑をセット */
	void setB(float b){ this->b = b; }/* 青をセット */
	void setRGB(float r, float g, float b){/* 赤緑青を同時にセット */
		this->r = r; this->g = g; this->b = b;
	}
	void print(){/* 色をプリント */
		cout << "(r,g,b) = (" << r << "," << g << "," << b << ")" << endl;
	}
	void print(int x, int y){/* ある画素位置と色をプリント */
		cout << "[" << x << "," << y << 
			"]: (r,g,b) = (" << r << "," << g << "," << b << ")" << endl;
	}
};
#endif

画像の頻度ヒストグラムをプリント

画像に関しては、エッジ抽出、エンハンスメント、セグメンテーションなど、 様々な演算が定義でき、最初に述べたOpenCVを使えば、 本当に簡単にプログラミングできるようになっています。 ここでは、もうひとつ、画像検索や画像分類にも利用できる画像特徴量の基本的な事例として、 カラーヒストグラム(ある色の濃淡範囲の画素の頻度)を作成する例を述べます。
histogram.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/* プログラム7-11 Histogramクラス */
#ifndef _HISTOGRAM
#define _HISTOGRAM
#include "jpegio.h"
#include <vector>

class Histogram { /* Histogramクラス */
	JpegImage jpg;/* JpegImageクラス */
	int numBin; /* ヒストグラムのビンサイズ */
	vector <vector <vector <int> > > hist;/* 3次元(RGB)ヒストグラム */
public:
	Histogram(){ numBin = 0; }/* 無引数コンストラクタ */
	Histogram(JpegImage jpg, int numBin) : jpg(jpg), numBin(numBin){
		hist.resize(numBin);/* 3次元動的配列のメモリ割当て */
		for (int i = 0 ; i < numBin ; i++)
			hist[i].resize(numBin);
		for (int i = 0 ; i < numBin ; i++) 
			for (int j = 0 ; j < numBin ; j++)
				hist[i][j].resize(numBin);
	}
	int getBin(){ return numBin; }
	vector <vector <vector <int> > > compRGBHist() /* ヒストグラムの計算 */{
		Color c;
		/* RGBヒストグラムの作成 */
		for ( int y = 0 ; y < jpg.iH() ; y++ ){
			for ( int x = 0 ; x < jpg.iW() ; x++ ){
				/* 座標(x,y)の画素の色値をゲットする */
				c = jpg.getColor(x,y);
				/* RGB3色の色反転を繰り返す */
				int b = (int)(numBin * c.B());/* 青成分に関数 */
				int g = (int)(numBin * c.G());/* 緑成分に関数 */
				int r = (int)(numBin * c.R());/* 赤成分に関数 */
				b = (b == numBin) ? numBin-1 : b;
				g = (g == numBin) ? numBin-1 : g;
				r = (r == numBin) ? numBin-1 : r;
				hist[r][g][b]++;/* ここで頻度をインクリメント */
			}
		}
		return hist;
	}
};
#endif

最初に使ったバスの画像例で、R, G, Bそれぞれ4段階に分割し、 合計4x4x4=64個のビンからなるヒストグラムを作成し、その頻度をCSV形式でプリントする例を述べます。
JpegHistMain.cpp
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/* プログラム7-12 Histogramクラス */
#include <iostream>
using namespace std;
#include "histogram.h"

int main(int argc, char **argv)
{
	/* 実行時引数から入力ファイル名を決定 */
  	if (argc < 2){
		cerr << "usage : " << argv[1] << endl;
		exit(1);
	}

	/* JPEGクラスの画像オブジェクト生成(読込)*/
	JpegImage imgIn;/* JPEGオブジェクト */

	/* JPEGライブラリを用いて画像データ入力 */
	imgIn.readJpeg(argv[1]);/* JPEGオブジェクトで読込み */

	/* ヒストグラムの生成 */
	int numBin = 4;/* ヒストグラムのビンサイズ:4(R,G,B各軸方向) */
	Histogram hist(imgIn, numBin);

	/* 入力画像のヒストグラムの計算 */
        vector <vector <vector <int> > > h = hist.compRGBHist();
	for (int i = 0 ; i < numBin; i++)
		for (int j = 0 ; j < numBin ; j++)
			for (int k = 0 ; k < numBin ; k++ )
				if (i == numBin-1 && j == numBin-1 && k == numBin-1)
					cout << h[i][j][k] << endl;
				else
					cout << h[i][j][k] << ",";
	return(0);
}
以下が実行結果です。

$ make
g++ -Wall -O3 -std=c++14 -c JpegHistMain.cpp
g++ -Wall -O3 -std=c++14 -o JpegHistMain JpegHistMain.o libjpeg.a

$ make run
./JpegHistMain 333.jpg
22616,2533,440,0,19,998,25,0,0,0,4,2,0,0,0,0,2340,180,26,0,4563,10727,
4494,5,0,28,1625,23,0,0,0,1,0,0,0,0,11739,894,182,0,2203,2559,8003,577,
0,0,7,73,0,0,0,0,1,0,0,0,2533,2994,6846,17,1,358,788,7880
小規模な画像検索を実装したい場合、たとえば、このようなヒストグラムを 100枚程度の画像で作成します。 検索においては、それ以外の画像を40枚程度用意して、類似度を比較します。 たとえば、以下2枚の画像が与えられたとします。

これらに、上述のプログラムで得られるヒストグラムをExcelで描画すると、 以下のような棒グラフが得られます。 最初の棒グラフがバスの画像に対するもので、2つ目の棒グラフが象のものです。
					バス画像のヒストグラム


					象画像のヒストグラム


このようなヒストグラムを与えられた画像データそれぞれで作成しておき、 そのデータをもとに、画像Aと画像Bの相違度を

dist(A,B)=\sum_{i}\sum_{j}\sum_{k} |hist_{A}[i][j][k] - hist_{B}[i][j][k]|

と定義し計算します。 類似度は、相違度の逆順で定義できるので、あるクエリ画像が与えられた場合、 そのクエリ画像とすべての検索対象画像との相違度を計算した後、 昇順にソーティングすることで類似度順のランクで画像を並べることができます。 要するに、ヒストグラム(頻度グラフ)に対応する棒グラフの分布形状が似ていれば、 画像が似ているとして検索結果をランキングするわけです。

テンプレートを使ったマージソート

ソーティングは、コンピュータサイエンスでは最も重要なアルゴリズムです。 これまでも、ヒープソート、クイックソート、 2分木のin-orderでのソート、課題でトポロジカルソートを述べてきました。 STLの中にもsort関数をもつものがあります。 ここでは、クイックソートとならび、再帰的に分割統治を使った高速なソーティングが保証されている、 マージソートを例にとって、テンプレートを使い、 ソートのキーとなる要素をメンバ変数で含む任意のクラスで適用可能なマージソートのプログラム例を紹介します。 まず、ソートの対象をGeoDistクラスとします。GeoDistクラスには、豊橋からの道路距離を表すdouble型のキーとなる変数distと、都市の名前を表すstring型の変数nameからなるとします。
GeoDist.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* プログラム7-13 GeoDistクラス */
#ifndef _GEODIST
#define _GEODIST
#include <iostream>
using namespace std;

class GeoDist {
 public:
	double dist;/* 距離 */
	string name;/* 名前 */
	/* 以下4つの比較演算子のオーバーロード */
	bool operator <   (const GeoDist &x){	return((dist < x.dist));}
	bool operator >   (const GeoDist &x){	return((dist > x.dist));}
	bool operator <= (const GeoDist &x){	return((dist <= x.dist));}
	bool operator >= (const GeoDist &x){	return((dist >= x.dist));}
	void print(){/* print演算 (動的にMergeSortクラスから呼ばれる)*/
 		cout << "(" << dist << "," << name << ") ";
	}
	GeoDist(){ dist = 0.0; name = "";}/* 無引数コンストラクタ */
	GeoDist(double dist, string name): dist(dist), name(name){}/* 2引数コンストラクタ */
};
#endif
MergeSort.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* プログラム7-14 マージソート クラス*/
#ifndef _MERGESORT
#define _MERGESORT
#include <iostream>
using namespace std;
#include <vector>

/* MergeSortクラスをテンプレートで作成 */
template <typename T>
class MergeSort {
public:
   MergeSort( vector <T> data ); /* コンストラクタ */
   void sort(); /* ソーティング関数 */
   void printElements() const; /*プリント:中身は変えないのでconst */
private:
   int size; /* vectorのサイズ */
   vector < T > data; /* T型の要素をもつvector*/
   void sortSubVector( int, int ); /* 部分vectorのソート */
   void merge( int, int, int, int ); /* 2つのソートされたベクトルのマージ */
   void printSubVector( int, int ) const; /* 部分vectorのプリント */
};
#include "MergeSort.cpp"
#endif
MergeSort.cpp
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
/* プログラム7-15 マージソート クラス(メンバー関数)*/
#include "MergeSort.h"

template <typename T>
MergeSort <T>::MergeSort( vector <T> v ){/* コンストラクタ */
   size = v.size(); /* サイズは引数のdataから設定 */
	for (int i = 0 ; i < size ; i++)
		data.push_back(v[i]);
}

/* vectorを分割、部分vectorをソート、結果をマージ */
template <typename T>
void MergeSort <T>::sort(){
   sortSubVector( 0, size - 1 ); /* 再帰的にソート */
} 

/* 再帰的に部分vectorをソート */
template <typename T>
void MergeSort <T>::sortSubVector( int low, int high ){
   /* vectorのサイズが1以上の場合 */
   if ( ( high - low ) >= 1 ) {
      int middle1 = ( low + high ) / 2; /* vectorの中央のインデックスを計算 */
      int middle2 = middle1 + 1; /* middle1の次の要素のインデックスを計算 */

      /* 2等分して分割統治 */
      sortSubVector( low, middle1 ); /* 前半 */
      sortSubVector( middle2, high ); /* 後半 */

      /* ソートされた部分vectorをマージ */
      merge( low, middle1, middle2, high );
   }
}

/* マージ本体 */
template <typename T>
void MergeSort <T>::merge( int left, int middle1, int middle2, int right ) {
   int leftIndex = left; /* 左部分vectorのインデックス */
   int rightIndex = middle2; /* 右部分vectorのインデックス */
   int combinedIndex = left; /* 作業用vectorのインデックス */
   vector < T > combined( size ); /* 作業用ベクトル */

   /* 部分vectorのどちらかの端に到達するまでの処理 */
   while ( leftIndex <= middle1 && rightIndex <= right ) {
      /* 小さい要素を作業領域に置き、次のスペースを探索 */
      if ( data[ leftIndex ] <= data[ rightIndex ] )
         combined[ combinedIndex++ ] = data[ leftIndex++ ]; 
      else 
         combined[ combinedIndex++ ] = data[ rightIndex++ ];
   }
   if ( leftIndex == middle2 ) {/* 左側の端 */
      while ( rightIndex <= right ) /* 右ベクトルの残りをコピー */
         combined[ combinedIndex++ ] = data[ rightIndex++ ];
   }
   else { /* 右ベクトルの端 */
      while ( leftIndex <= middle1 ) /* 左ベクトルの残りをコピー */
         combined[ combinedIndex++ ] = data[ leftIndex++ ];
   }
   /* 作業vectorから元のvectorにコピー・バック */
   for ( int i = left; i <= right; i++ ) data[ i ] = combined[ i ];
}

template <typename T>
void MergeSort <T>::printElements() const { /* データのプリント */
   printSubVector( 0, size - 1 );
}

template <typename T>
void MergeSort <T>::printSubVector( int low, int high ) const {
	for ( int i = 0; i < low; i++ )   /* alignment用スペース */
		cout << "   ";
	for ( int i = low; i <= high; i++ ){/* T型変数に代入してからprintメソッドを呼び出し */
	T gd= data[i];
		gd.print();/* print関数は、T型クラスにあると想定 */
	} 
}
注意すべきは、テンプレートを利用しているため、クラス定義自体と、 クラス内のメンバー関数の定義を「分割してコンパイルできない」ことです。 これはテンプレートの制限でもありますが、データ型を一般化しているための制限だと理解してください。 上述のヘッダーファイルを仮定したmain関数を含むプログラム は以下のようです。
MergeMain.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/* プログラム7-16 マージソート:mainプログラム */
#include <iostream>
using namespace std;
#include "GeoDist.h"
#include "MergeSort.h"

int main() {
	vector <GeoDist> sortVector {/* vector型の変数 */
		GeoDist(256.0, "大阪"), GeoDist(46.0, "岡崎"), 
		GeoDist(80.0, "名古屋"), GeoDist(200.0, "京都"),  
		GeoDist(277.0,"横浜"), GeoDist(33.0, "浜松"), 
		GeoDist(414.0, "倉敷"), GeoDist(321.0, "富山"), 
		GeoDist(651.0, "仙台"), GeoDist(0.0, "豊橋")
	};
	MergeSort <GeoDist> ms(sortVector);/* GeoDist型のMergeSortクラス変数の定義 */

	cout << "ソート前:" << endl;
	ms.printElements(); /* print unsorted vector */
	cout << endl << endl;

	ms.sort(); /* マージソート本体の呼び出しr */

	cout << "ソート後:" << endl;
	ms.printElements(); /* print sorted vector */
	cout << endl;
	return 0;
}
この関数ならびに演算子のオーバーロードとfriend関数(演算)を用いたmainプログラムの例を以下に示します。 なお、コンパイルする場合に、-std=c++11(または-std=c++14)が必要になりますので注意してください。 これを実行した結果は以下のようです。

$ make
g++ -Wall -O3 -std=c++14 -c MergeMain.cpp
g++ -Wall -O3 -std=c++14 -o MergeMain MergeMain.o

$ make run
./MergeMain
ソート前:
(256,大阪) (46,岡崎) (80,名古屋) (200,京都) (277,横浜) (33,浜松) (414,倉敷)
 (321,富山) (651,仙台) (0,豊橋)

ソート後:
(0,豊橋) (33,浜松) (46,岡崎) (80,名古屋) (200,京都) (256,大阪) (277,横浜)
 (321,富山) (414,倉敷) (651,仙台)

デザインパターン

C++に代表されるオブジェクト指向言語で、プログラム開発やシステム開発を行うに当たり、 デザインパターンを念頭に置く考え方が広く採用されています。 よく知られているデザインパターンのいくつかは、STLに採用されています。 その代表例はIterator(イタレータ)を用いるパターンで、リスト、集合、 ハッシュ表などに登録されたデータをアクセスする際の標準的なクラスを与えています。 代表的な23個のデザインパターンを列挙すると以下のようです。 ちなみに、英語や日本語のデザインパターンのWikiにもあります。

番号 デザインパターンの種類
1Singletonパターン
2Facadeパターン
3Compositeパターン
4Template Methodパターン
5Factory Methodパターン
6Iteratorパターン
7Strategyパターン
8Adapterパターン
9Observerパターン
10Mediatorパターン
11Flyweightパターン
12Proxyパターン
13Prototypeパターン
14Mementoパターン
15Decoratorパターン
16Commandパターン
17Stateパターン
18Chain of Responsibilityパターン
19Builderパターン
20Bridgeパターン
21Abstract Factoryパターン
22Interpretoerパターン
23Visitorパターン

代表的な例としてSingletonパターンを取り上げます。 これは、たった一つだけオブジェクト生成を許すというデザインパターンです。 C言語でstaticを使ったグローバル変数を使ったことがある人も多いかと思いますが、 通常オブジェクト指向言語で、クラス内にやたらとstatic変数を準備することは、 決して「よいオブジェクト指向プログラミング」とは言えません。 とはいえ、グローバル変数を個々のクラスに持たせるのではなく、 Singletonデザインパターンとして用意し、 Singletonクラスのインスタンスを1個だけ生成を許し、そこにグローバル変数を持たせる、 という考え方がしばしば利用されます。

その例として、再帰的関数の呼び出し回数をカウントさせる変数を、 Singletonクラス内の変数として持たせる例を紹介します。
Singleton.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/* プログラム7-17 Singletonクラス実装例*/
#ifndef _SINGLETON
#define _SINGLETON

class Singleton{/* Singletonデザインパターン */
private:
    static Singleton* myInstance;  /* 唯一のインスタンスはポインタとする */
    long counter;/* カウンタ(大きな値をカウントしたいためlong型としている) */
public:
    static Singleton* GetInstance()  {/* 唯一の公開アクセス法 */ 
    	if ( myInstance == 0 ){
    		myInstance = new Singleton();/* 唯一のオブジェクト生成 */
    		myInstance->counter = 1;
    	}
    	return myInstance;
    }
    void increment(){ counter++; }/* インクリメント */
    void reset(){ counter = 0; }/* カウンタのリセット */
    long get(){ return counter; }/* カウンタのゲット */
private: /* オブジェクトの直接生成、コピー生成、代入生成を禁じる */
    Singleton(){}/* コンストラクタはprivate */
    Singleton( const Singleton& x );/* コピーコンストラクタもprivate */
    Singleton& operator = ( const Singleton& x );/* 代入演算もprivate */
};
Singleton* Singleton::myInstance = 0;/* static変数の実体 */
#endif
まず、7行目で、privateなアクセスで、staticな変数myInstanceが定義されています。 static修飾子がつく変数や関数は、一般にメモリ内の静的な領域に確保されますので、 オブジェクトを生成することなくアクセスしたいときに有用です。 逆に言うと、静的な領域にデータが確保されるため、 オブジェクトを動的に生成し、動的に削除する、といった一般的なオブジェクト指向プログラミングに反する使用法といえます。 とはいえ、数学関数のsin, cosのように常にどこかに常駐していて、いろいろなクラスやmain関数等から共有して使いたい関数や変数 を準備したい場合があります。 共有したいオブジェクトを定義する一つの方法がSingletonデザインパターンで実現できることになります。 共有するため、複数のオブジェクトがあってはならないため、まず7行目の変数と10行目にあるGetInstance関数のような 静的な関数を定義しておきます。 GetInstance関数では、12行目にあるようにSingletonオブジェクトを生成します。 ただし、カウントがゼロのときのみ生成することとし、2つ以上のオブジェクトが生成できないようにしておきます。

一方、7行目のmyInstanceの実体はSingletonオブジェクト生成とは別に定義しておく必要があります。 それが25行目にあり、classの定義の外で行っている点に注意してください。 このためSingleton::という名前空間もつけています。

また、Singletonオブジェクトを2個以上生成させないようにするため、 コンストラクタを呼び出されないように、21行目で、private領域にコンストラクタを定義しておきます。 これにより、継承クラスでも、他クラスからも、あるいはmain関数からもコンストラクタが呼ばれることはありません。 同様に、コピーコンストラクタも22行目で禁止しており、代入演算も23行目で禁止しておきます。

Singletonクラスのオブジェクトを利用するプログラム例として、再帰的にフィボナッチ数列を呼び出す関数の呼び出し回数を カウントするプログラム例を示します。
Singleton.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/* プログラム7-18 Singletonオブジェクトをグローバルカウンタとして利用する例*/
#include <iostream>
using namespace std;
#include "Singleton.h"
Singleton *anInstance;/* シェアしたいオブジェクト */

/* フィボナッチ数の再帰的定義 */
long Fibonacchi (int n){
	if ( n <= 0 ) return((long)0);
	else if (n == 1 || n == 2) return((long)1);
	else {/* 2回再帰的に呼び出す */
		anInstance->increment();
		anInstance->increment();
		return( Fibonacchi(n-1) + Fibonacchi(n-2)  );
	}
}

int main(){
	anInstance = Singleton::GetInstance();
	cout << "カウンタ値(スタート) " << anInstance->get() << endl;
	long n;
	cout << "フィボナッチ数のパラメータ(N)を入力してください >";
	cin >> n;
	Fibonacchi(n);
	cout << "カウンタ値(フィボナッチ数が再帰的に呼ばれた回数) = " << 
		anInstance->get() << endl;
	return 0;
}
実行例は以下のようです。N=40ですでに2億回以上呼び出されることがわかります。

$ g++ -Wall -std=c++14 -o Singleton Singleton.cpp
$ ./Singleton
カウンタ値(スタート) 1
フィボナッチ数のパラメータ(N)を入力してください >40
カウンタ値(フィボナッチ数が再帰的に呼ばれた回数) = 204668309

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

コンストラクタを含め関数のオーバーロードはJava言語でも 今後、出くわすと思います。一方、演算子のオーバーロードは、Python言語にはあるもののJava言語になかったりしますが、慣れるととても有用な機能です。 すでに、複素数や行列で演算子のオーバーロード例をみてきましたが、 以下では、巨大な整数を扱えるHugeIntクラスを作成し、その中で 演算子のオーバーロードをとりあげます。 なお、この例で、C++特有のfriend(フレンド)演算(関数)を使います。 friend関数は、通常のメンバ関数と大きく異なります。 メンバ関数は、それが定義されていうクラスに属する関数で、 クラスがたとえばxという変数でインスタンスが作られるとすると、x.func()のようにアクセスできます。 一方、friend関数(演算)は、 実際は違うクラスまたは外部で定義された関数(演算)でそのクラスには属しません。 しかし、クラス内でfriend宣言された関数(演算)はクラス内のメンバ変数に、そっとアクセスすることができます。 friend宣言自体、 通常のアクセス制限子(private, protected, public)を飛び越える概念であり、 Java言語にはない機能ですので、移植性などを考えれば、 できるだけ利用は控えるほうが賢明です。とはいえ、C++言語で閉じているシステムでは、 演算子のオーバーロード、仮想関数、friend宣言など、便利なものは、 TPOに応じて利用すればいいかと思います。
HugeInt.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/* プログラム7-19 HugeInt.h (Deitelの教科書を参考にしたもの)*/
#ifndef HUGEINT_H
#define HUGEINT_H
#include <iostream>
#include <string>
using namespace std;
#include <exception>

class HugeInt { /* 巨大な整数(足し算のみ)を扱えるクラス */
  /* HugeIntクラスでない演算子のオーバーロードなのでfriend宣言する */
  friend ostream &operator<<( ostream &, const HugeInt & );
public:
   static const int digits = 1000; /* HugeIntクラスの最大ディジット*/
private:
   short integer[ digits ];/* この配列に大きい整数をひとつずつ入れていく */
public:
   HugeInt( long = 0 ); /* デフォルトコンストラクタ */
   HugeInt( const string & ); /* 文字列で長~い整数を入力するコンストラクタ */
   HugeInt operator+( const HugeInt & ) const;/* HugeInt+HugeInt */
   HugeInt operator+( int ) const;     /* HugeInt + int */
   HugeInt operator+( const string & ) const;    /* HugeInt + 文字列整数 */
};

/* 例外処理クラスをexceptionの派生クラスとして定義 */
class OverflowException : public exception {
	public: virtual const char *what() const throw() {	return("大整数の桁あふれです。"); }
};
class FormatException : public exception {
	public: virtual const char *what() const throw() {	return("整数でない文字列です。"); }
};
#endif
プログラム7-19はヘッダーファイルで、これに対応するメンバー関数とfriend関数の定義 は以下のようです。
HugeInt.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/* プログラム7-20 HugeInt.cpp */
/* メンバー関数とfriend関数を定義 */
#include "HugeInt.h"
#include <cctype> /* isdigit関数を使う */

/* デフォルトコンストラクタ*/
HugeInt::HugeInt( long value ){
   for ( int i = 0; i < digits; i++ )/* 配列をゼロで初期化 */
      integer[ i ] = 0;   
   /* valueを配列に置く */
   for ( int j = digits - 1; value != 0 && j >= 0; j-- ) {
      integer[ j ] = value % 10;
      value /= 10;
   }
}
/* 文字列から長い(大きい)整数を作成するコンストラクタ */
HugeInt::HugeInt( const string &number ){
   for ( int i = 0; i < digits; i++ )/* 配列をゼロで初期化 */
      integer[ i ] = 0;
   /* 文字列内の値を整数に変換して配列に置く */
   int length = number.size();/* 文字列の長さを計算する */
   if (length > digits) throw OverflowException();
   for ( int j = digits - length, k = 0; j < digits; j++, k++ )
      if ( isdigit( number[ k ] ) ) /* digitかどうかチェック */
         integer[ j ] = number[ k ] - '0';
      else throw FormatException();
}
/* HugeInt + HugeIntの加算演算 */
HugeInt HugeInt::operator+( const HugeInt &op2 ) const {
   HugeInt temp; /* 一時変数 */
   int carry = 0;
   for ( int i = digits - 1; i >= 0; i-- ) {
      temp.integer[ i ] = integer[ i ] + op2.integer[ i ] + carry;
      /* 切り上げ(キャリー)が1かどうか */
      if ( temp.integer[ i ] > 9 )  {
         temp.integer[ i ] %= 10;  /* 0-9に帰着 */
         carry = 1;
      }
      else /* キャリーなし */
         carry = 0;
   }
   return temp; /* 一時オブジェクトを返す */
}
/* HugeInt + int */
HugeInt HugeInt::operator+( int op2 ) const { 
   /* op2をHugeIntに変換し、加算演算+を実行 */ 
   return *this + HugeInt( op2 ); 
}
/* HugeInt + string */
HugeInt HugeInt::operator+( const string &op2 ) const { 
   /* op2をHugenIntに変換し、2つのHugeIntで+を実行 */ 
   return *this + HugeInt( op2 ); 
}
/* friend関数(演算)の実装 */
ostream& operator<<( ostream &output, const HugeInt &num ){
   int i;
   /* 先頭のゼロをスキップ */
   for ( i = 0; ( num.integer[ i ] == 0 ) && ( i <= HugeInt::digits ); i++ );
   if ( i == HugeInt::digits )
      output << 0;
   else
      for ( ; i < HugeInt::digits; i++ )
         output << num.integer[ i ];
   return output;
}
この関数ならびに演算子のオーバーロードとfriend関数(演算)を 用いたmainプログラムの例を以下に示します。
TestHugeInt.cpp
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/* プログラム7-21  HugeIntのテスト */
#include "HugeInt.h"

int main( void )
{
	HugeInt n1( 39074 );
	HugeInt n2( 29631 );
	HugeInt n3;
	n3 = n1 + n2;
	cout << n1 << " + " << n2 << " = " << n3 << endl << endl;
	/* 円周率(整数化)+ネイピア数(整数化) 51ケタ同士の加算 */
	try {
		HugeInt pi("314159265358979323846264338327950288419716939937510"); 
		HugeInt  e("271828182845904523536028747135266249775724709369995"); 
		HugeInt n4;
		n4 = pi + e;
		cout << pi << " + " << endl << e << " = " << endl << n4 << endl;
	}
	catch (OverflowException &e ){
		cout << e.what() << endl;
	}
	catch (FormatException &e){
		cout << e.what() << endl;
	}
	return 0;
}
これを実行した結果は以下のようです。

$ make
g++ -Wall -O3 -std=c++14 -c TestHugeInt.cpp
g++ -Wall -O3 -std=c++14 -c HugeInt.cpp
g++ -Wall -O3 -std=c++14 -o TestHugeInt TestHugeInt.o HugeInt.o

$ make run
./TestHugeInt
39074 + 29631 = 68705

314159265358979323846264338327950288419716939937510 +
271828182845904523536028747135266249775724709369995 =
585987448204883847382293085463216538195441649307505

STL <vector>とiteratorを使った例

STLの例は、mapや vectorなどを紹介してきましたが、 これらのデータが可変数個得られたとき、 vectorでは配列的なデータ指定方法で反復することができますが、 iteratorを使っても反復することができます。 特にlistやmapのようなSTLの場合、配列的なアクセスはそもそもできないため、 反復する場合、iteratorが必須となります。

以下では、vectorとiteratorを組合わせる例として、2次元図形クラスの継承例を紹介します。
ShapeMain.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
/* プログラム7-22 */
	[ファイル]	ShapeMain.cpp
	[内容]		2次元の多角形と円を複数個(乱数を用いて)作成し、
			これをSTLのvector に保持し、PostScriptファイルとして
			出力するプログラム(グローバル変数と関数は明示的に::ではじめる)
*/
using namespace std;
#include <iostream>
#include <cstdlib>
#include <ctime>
#include "Polygon.h"
#include "Circle.h"
#include <typeinfo>
#include <vector>

const double XRANGE = 580.0;//PSキャンパスの横サイズ
const double YRANGE = 700.0;//PSキャンパスの縦サイズ
const double RGBRANGE = 1.0;//乱数のスケールファクタ
const int MAXEDGE = 10;//多角形の最大エッジ数
const int MINEDGE = 3;//多角形の最小エッジ数

void psHead(){/* PostScriptのヘッダー:なくても可 */
	cout << "%!PS-Adobe-2.0" << endl; 
	cout << "%%File: kadai4.ps" << endl;
	cout << "%%Author: 青野 雅樹: 01162069" << endl;
}

void psTail(double a, double b){ /* PostScriptのテイラー:必須 */
	cout << "%% 総面積は " << a << "です" << endl;
	cout << "%% 総長は " << b << "です" << endl;
	cout << "showpage" << endl;
}

double random(double min, double max){/* [min,max]の間のdouble型の乱数発生 */
	double value = (double) ((max - min)  * rand() / (double)RAND_MAX + min);
	return value;
}
int random(int min, int max){// [min,max]の間の乱数を発生(関数のオーバーロード)
	int range = max - min + 1;
	int value = (rand() % range) + min;
	return value;
}
int main(int ac, char **av){
	double x[::MAXEDGE], y[::MAXEDGE], R,G,B;
	vector <Shape*> data;/* 可変長のデータ */
	
	int n = atoi(av[1]);/* 生成する図形のペア総数を入力させる */
	int m; /* 多角形の辺の数 */
	double allArea = 0.0;
	double totalLength = 0.0;
	srand(time(nullptr));/* 乱数の初期化 */
	for (int j = 0 ; j < n ; j++){ 
		m = ::random(::MINEDGE,::MAXEDGE);/* 多角形の頂点数を決定 */
		R = ::random(0.0, ::RGBRANGE);/* 赤色成分を決定 */
		G = ::random(0.0, ::RGBRANGE);/* 緑色成分を決定 */
		B = ::random(0.0, ::RGBRANGE);/* 青色成分を決定 */
		for (int i = 0 ; i < m ; i++ ){
			x[i] = ::random(0.0, ::XRANGE);/* X座標を決定 */
			y[i] = ::random(0.0, ::YRANGE);/* Y座標を決定 */
		}
		switch ( j % 2 ){
			case 0:{/* 多角形クラスのインスタンスを生成 */
				Polygon *p = new Polygon(m, x, y, R, G, B);
				data.push_back(p);/* 可変長vectorに多角形を追加 */
				break;
			}	
			case 1:{/* 円クラスのインスタンスを生成 */
				Circle *c = new Circle(x[0], y[0], x[1]/4, R, G, B);
				data.push_back(c);/* 可変長vectorに円を追加 */
				break;
			}
		}
	}

	/* 出力 */
	::psHead();/* ヘッダ部分のプリント */
	int count = 1;
	vector <Shape *>::iterator it = data.begin();
	for (; it != data.end() ; it++){/* dataから、形状クラスのpsPrint()を呼び出す */
		Shape *shape = (Shape *)(*it);/* 形状クラスのポインタを取り出す */
		const type_info& info = typeid(*shape);
		cout << "%%" << count++ << "番目の図形は";
		if (info == typeid(Circle)) cout << "円";
		else if (info == typeid(Polygon)){
			Polygon* polygon = (Polygon *)shape;
			cout << polygon->getNumVertices()  << "角形";
		}
		cout << "です" << endl;
		shape->print();/* ここで図形に関わらず、派生クラスのpsPrint()メソッドが実行される */
		allArea += shape->area();
		totalLength += shape->perimeter();
	}
	::psTail(allArea, totalLength);/* フッタ部分のプリント */
	return 0;
}


テンプレートを使った簡単な四則演算カリキュレータ

この例は、もともとC++ in a Nutshellという本にあるサンプルプログラムを改良したものです。 C言語やC++言語では、ポインタに関しては、動的に生成すると、deleteやデストラクタでメモリを 解放する必要があります。 この煩わしさから多少してくれるのが、 shared_ptr <型名(クラス名)>というテンプレートです。 shared_ptrを使う場合は、delete関数を呼び出さなくても、システムが勝手にメモリを解放してくれます。このようなポインタ型変数をスマートポインタと呼ぶことがあります。 スマートポインタは、C+11以降機能追加されたもので、shared_ptrのほかに、unique_ptr、auto_ptr, weak_ptrがあります。auto_ptrはC++98のレガシーのポインタとの互換性で使われることが多く、通常は、unique_ptrで代用できます。weak_ptrはshared_ptrとペアで使われるもので、単独でポインタとして利用せず、shared_ptrがポイントしていたオブジェクトが消滅した場合に、消滅したかどうかの確認用に補助的に使用されます。

この例では、低レベルの入出力であるbasic_istreamやbasic_ostreamで文字単位の入力や出力を行います。 このために、C++言語に備わっている <charT, traits>というテンプレートも普通に表れますが、これらを除けば、クラスの継承や純粋仮想関数もあり、C++言語らしいプログラムの例になっているかと思います。 なお、括弧を含む四則演算数式の評価を行うため、このプログラムでは、式は以下のBNF (Backus Naur Form)で書かれるものとなっています。

<expression>
	 ::= <simple-expression>
<simple-expression> 
	::= <term>
	::= <additive-expression>
<additive-expression> 
	::= <term> '+'|'-' <simple-expression>
<term> 
	::= <multiplicative-expression>
	::= <factor>
<multiplicative-expression> 
	::= <factor> '*'|'/' <term>
<factor> 
	::= '(' <expression> ')'
	::= literal-constant
Expr.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/* プログラム7-23 Expr.h */
#ifndef _EXPR_H
#define _EXPR_H

#include <exception>
#include <iostream>
#include <istream>
#include <limits>
#include <memory>
#include <ostream>
#include <sstream>
#include <stdexcept>
#include <string>
#include <cstdlib>
using namespace std;
#include "ParseException.h"

#define charTraits template<typename charT, typename traits>
#define inTraits basic_istream<charT, traits>
#define outTraits basic_ostream<charT, traits>
#define staticExpr static shared_ptr<Expr>

class Expr {
protected:
  charTraits staticExpr simpleExpr(inTraits& in);/* simpleExpr */
  charTraits staticExpr term(inTraits& in);/* term */
  charTraits staticExpr factor(inTraits& in);/* factor */
  charTraits friend outTraits& operator<<(outTraits& out, Expr& e);
public:
  ~Expr() {}
  charTraits staticExpr expression(inTraits& in);/* expression */
  void print(ostream& out) {}
  virtual double evaluate() =0;/* 評価関数evaluate()は純粋仮想関数 */ 
  virtual int precedence() = 0;/* 優先度関数precedence()は純粋仮想関数 */ 
};
#endif
Literal.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
/* プログラム7-24 Literal.h */
#ifndef _LITERAL_H
#define _LITERAL_H
#include "Expr.h"

class Literal : public Expr {
private:
  double _value;
public:
  Literal(double value) : _value(value) {}/* コンストラクタ */
  virtual double evaluate() { return _value; }
  virtual void print(ostream& out) { out << _value; }
  virtual int precedence()  { return 1; }/* リテラルは優先度1 */
};
#endif
BinaryOp.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/* プログラム7-25 BinaryOp.h */
#ifndef _BINOP_H
#define _BINOP_H
#include "Expr.h"

class BinaryOp : public Expr {
private:
  BinaryOp(const BinaryOp&);/* コピーコンストラクタ */
  void operator=(const BinaryOp&);/* 演算子オーバーローディング */
  shared_ptr <Expr> left_;/* left_ポインタはshared_ptrで自動削除 */
  shared_ptr <Expr> right_;/* right_ポインタはshared_ptrで自動削除 */
protected:
  virtual double eval(double left, double right) =0; /* 2項演算:派生クラスでの純粋仮想関数 */
  virtual const char* op() = 0;/* 2項演算子:派生クラスでの純粋仮想関数 */
public:
  BinaryOp(shared_ptr<Expr> left, shared_ptr<Expr> right) : left_(left), right_(right) {}/* コンストラクタ */
  virtual void print(ostream& out) {/* 演算子の優先順位に応じてプリント */
    if (left_->precedence() > precedence()) out << '(' << *left_ << ')';
    else  out << *left_;
    out << op();
    if (right_->precedence() > precedence()) out << '(' << *right_ << ')';
    else  out << *right_;
  }
  virtual double evaluate() { return eval(left_->evaluate(), right_->evaluate()); }
  virtual int precedence() = 0;/* これは更に派生クラスに実装を依頼 */
};
#endif
Plus.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
/* プログラム7-26 Plus.h */
#ifndef _PLUSOP_H
#define _PLUSOP_H
#include "Expr.h"
#include "BinaryOp.h"

class Plus : public BinaryOp {
protected:
  virtual double eval(double left, double right) { return left + right; }/* 加算 */
  virtual const char* op() { return "+"; }/* 加算記号 */
public:
  Plus(shared_ptr<Expr> left, shared_ptr<Expr> right) : BinaryOp(left, right) {}
  virtual int precedence()  { return 3; }/* 優先度3 */
};
#endif
Minus.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
/* プログラム7-27 Minus.h */
#ifndef _MINUSOP_H
#define _MINUSOP_H
#include "Expr.h"
#include "BinaryOp.h"

class Minus : public BinaryOp {
protected:
  virtual double eval(double left, double right) { return left - right; }/* 減算 */
  virtual const char* op() { return "-"; }/* 減算記号 */
public:
  Minus(shared_ptr<Expr> left, shared_ptr<Expr> right) : BinaryOp(left, right) {}
  virtual int precedence()  { return 3; }/* 優先度3 */
};
#endif
Times.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
/* プログラム7-28 Time.h */
#ifndef _TIMESOP_H
#define _TIMESOP_H
#include "Expr.h"
#include "BinaryOp.h"

class Times : public BinaryOp {
protected:
  virtual double eval(double left, double right){ return left * right; } /* 乗算 */
  virtual const char* op()  { return "*"; }/* 乗算記号 */
public:
  Times(shared_ptr<Expr> left, shared_ptr<Expr> right) : BinaryOp(left, right) {}
  virtual int precedence() { return 2; }/* 優先度 2 */
};
#endif
Divide.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
/* プログラム7-29 Divide.h */
#ifndef _DIVIDEOP_H
#define _DIVIDEOP_H
#include "Expr.h"
#include "BinaryOp.h"

class Divide : public BinaryOp {
protected:
  virtual double eval(double left, double right) { return left / right; }/* 除算 */
  virtual const char* op()  { return "/"; }/* 除算記号 */
public:
  Divide(shared_ptr<Expr> left, shared_ptr<Expr> right) : BinaryOp(left, right) {}
  virtual int precedence() { return 2; }/* 優先度 2 */
};
#endif
ParseException.h
1 
2 
3 
4 
5 
6
7
8
9
10
/* プログラム7-30 ParseException.h */
#ifndef PARSE_EXCEPTION_H
#define PARSE_EXCEPTION_H
class ParseException : public runtime_error {/* パーズ例外処理 */
public:
  ParseException(char c) : 
  	runtime_error(string("シンタックスエラー:") + c + "の近くです") {}
  ParseException(const string& msg) : runtime_error(msg) {}
};
#endif
このように、リテラル、2項演算(加算、減算、乗算、除算)のクラスを定義し、最終的には、端末から数式を入力させ、入力された式を評価して結果をプリントするのが、このプログラムの主たる役目です。 この際、入力された式は、BinaryOp.hに定義されているように、 リテラル以外は2項演算しかないので、演算子がある場合は、 演算子の左側の式と右側の式を、 のように<Expr>のshared_ptr型(大雑把にポインタと思えばよい)で保持させます。 このに<Expr>のshared_ptr型から、実際に評価して実数値を求めるのが、evaluate()メソッド(メンバ関数)となります。 BinaryOp.hの24行目にあるように、これら2つが結びついています。 evaluate()関数は、最終的にeval()関数を呼出し、 演算子に応じたeval()関数で最終的な実数値が求まります。
Expr.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
/* プログラム7-31 Expr.cpp */
#include "Expr.h"
#include "Literal.h"
#include "BinaryOp.h"
#include "Plus.h"
#include "Minus.h"
#include "Times.h"
#include "Divide.h"
#include "ParseException.h"

charTraits 
shared_ptr<Expr> Expr:: factor ( inTraits& in ) {
	charT c;/* 入力文字 */
	in >> c;
	if (!in) throw ParseException("予期せぬEOF");
	if (c == '(') {/* 左括弧で式がはじまるとき */
		shared_ptr<Expr> rtn(expression(in));/* expression処理 */
		if (! (in >> c)) throw ParseException("予期せぬEOF");
		else if (c != ')') throw ParseException(string("右括弧がありません, かわりに ") + c);
		else return rtn;/* 式の評価結果を返す */
	} else {
		double x;
		in.unget();
		in >> x;
		return shared_ptr<Expr>(new Literal(x));/* リテラル(定数)処理 */
	}
}

charTraits
shared_ptr<Expr> Expr:: term ( inTraits& in ){
	shared_ptr<Expr> left(factor(in));/* factor処理 */
	charT c;
	in >> c;
	if (! in) return left;
	if (c != '*' && c != '/'){/*乗算または除算でないときは入力文字を戻す */
		in.unget();
		return left;
	}
	shared_ptr<Expr> right(term(in));/* term処理 */
	switch (c) {
		case '*': return shared_ptr<Expr>(new Times(left, right));/* 乗算 */
		case '/': return shared_ptr<Expr>(new Divide(left, right));/* 除算 */
		default:  throw ParseException(string("termでエラー: c == ") + c);
	}
}

charTraits
shared_ptr<Expr> Expr:: simpleExpr ( inTraits& in ){
	shared_ptr<Expr> left(term(in));/* term処理 */
	charT c;
	in >> c;
	if (! in) return left;
	if (c != '+' && c != '-')  {/* 加算でも減算でもない場合は入力文字を戻す */
		in.unget();
		return left;
	}
	shared_ptr<Expr> right(simpleExpr(in));/* simpleExpr処理 */
	switch (c) {
		case '+': return shared_ptr<Expr>(new Plus(left, right));/* 加算 */
		case '-': return shared_ptr<Expr>(new Minus(left, right));/* 減算 */
		default:  throw ParseException(string("simpleExprでエラー: c == ") + c);
	}
}

/* expressionメンバー関数 */
charTraits
shared_ptr<Expr> Expr:: expression ( inTraits& in ){
	shared_ptr<Expr> rtn(simpleExpr(in));/* expression ::= simpleExpr */
	return rtn;
}

/* friend演算子の定義 */
charTraits 
outTraits& operator<<(outTraits& out, Expr& e){
	e.print(out);
	return out;
}
/* メイン関数 */
using ExprFunc = double (Expr::*) (); // C++11

int main( void ){
  	do /* 無限ループ開始 */
		try { /* 例外処理 */
			string line;
			cout << "> ";/* プロンプト */
			if (! getline(cin, line)) break;/* 行単位で数式を読み込み */
			istringstream in(line);

			in >> ws;/* white spaceをスキップ */
			if (in.eof()) continue;/* 何も入力されない場合は入力に戻る */

			shared_ptr<Expr> e(Expr::expression(in));/* expressionの評価結果を返す */
			in >> ws;/* white spaceをスキップ  */
			if (! in.eof()) {
				shared_ptr<Expr> e2(Expr::expression(in));
				cout << *e2 << '\n';/* expression評価結果を表示 */
				cerr << "式の最後にシンタックスエラーがあります" << std::endl;
			}
			cout << *e << '\n';/* expression評価結果を表示 */
			/* evaluateというdoubleを返すメンバ関数のポインタfuncを宣言 */
			ExprFunc func = &Expr::evaluate;
			/* Type (expr*) *get() const throw(): shared_ptrのgetで型のポインタ値をゲット */
			cout << (e.get()->*func)() << '\n';/* 式を評価した結果をプリント */
		} catch (ParseException& pe) {/* パーズエラーの場合 */
			cerr << pe.what() << '\n';
		} catch (exception& ex) {/* その他の例外の場合 */
			cerr << ex.what() << '\n';
			abort();
		} 
	while (cin);/* 入力がある限り無限ループ: Ctrl+D, Ctrl+Cで終了 */
}
Expr.cppでは、BNFで述べた、factor関数、term関数、 simpleExpr関数、ならびにexpression関数が定義されています。 main関数からは、inから入力される式を91行目のshared_ptr e(Expr::expression(in)); でeという名前の変数でExprクラスのstaticの関数Expr::expression(in)が呼び出され、 expressionとしての処理を行います。 Exprクラスには、コンストラクタがないことに注意してください。 その代り、staticな関数があるため、オブジェクトのインスタンスなしに直接呼び出すことができます。

99行目の double (Expr::* func)() = &Expr::evaluate; は、メンバ関数evaluateへのポインタをfuncという変数名に代入しています。 最終的に式を評価するのは、101行目の(e.get()->*func)() です。 ここで、e.get()は、shared_ptrに用意されている get関数でshared_ptrが指すポインタを返します。eは式(の文字列=charTraits型)を指すポインタでしたから、 これにevaluate関数を適用すれば、式を評価した実数値が得られます。 それが103行目の(e.get()->*func)()で実行されるわけです。 結果として、charTraits shared_ptr<Expr> Expr::expression(in)にevaluate関数を適用していることになります。 実行例は、以下のようです。

$ make
g++ -Wall -O3 -std=c++14 -c Expr.cpp
g++ -Wall -O3 -std=c++14 -o Expr Expr.o

$ make run
./Expr
> 1+5*4

21
> -35 + (1+1/4+1/5+1/128)

-33.5422
> (1-1/4)*(1+1/4)

0.9375
> 1-1/16

0.9375