第14章 テンプレート(Javaを除く)

14.1 関数テンプレート
(プログラム例 14.1 ) 関数テンプレート
14.2 クラステンプレート
(プログラム例 14.2 ) クラステンプレート
14.3 C++ 標準ライブラリと STL ( Standard Template Library )
14.3.1 コンテナ
14.3.2 イテレータ(反復子)
(プログラム例 14.3 ) イテレータ
(プログラム例 14.4 ) 挿入イテレータ
(プログラム例 14.5 ) ストリームイテレータ
14.3.3 アルゴリズム( STL )
14.3.4 関数オブジェクト
(プログラム例 14.6 ) map における関数オブジェクトの使用
(プログラム例 14.7 ) 関数オブジェクトの作成
演習問題14

14.1 関数テンプレート

  例えば,2 つの値の大きさを比べ,小さい方を返す関数について考えてみます.今までの方法では,各引数の型毎に関数を定義しなければなりません(関数名のオーバーロード).しかし,次の形式で書かれる関数テンプレート( template )を使用することによって,1 つの関数の定義だけですますことが可能です
	template <テンプレート引数宣言> 関数宣言または定義
		
  テンプレート引数は関数宣言や定義の中で型名として使用でき,この型が任意の型に変換されます.例えば,次の例では,型 cl の部分に double や int が入ります.クラス型の変数に対しても次の関数を使用できますが,そのクラスのオブジェクトの大きさを比較する演算子「 < 」が定義されていなければ成りません.また,関数の 2 つの引数は同じ型 cl ですので,異なった型との比較はエラーになります.

(プログラム例 14.1 ) 関数テンプレート
/****************************/
/* 関数テンプレート         */
/*      coded by Y.Suganuma */
/****************************/
#include <iostream>

/*****************************/
/* 様々な型のデータの比較    */
/*      a,b : 比較するデータ */
/*****************************/
template <class cl> cl min(cl a, cl b) {
	return a < b ? a : b;
}

/******************/
/* mainプログラム */
/******************/
int main()
{
	int i = 0, j = 1, k;
	double x = 10.0, y = 5.5, z;

	k = min(i, j);
	z = min(x, y);

	std::cout << k << "  " << z << std::endl;

	return 0;
}
		
14.2 クラステンプレート

  クラスに対しても,関数と同様,以下のような形式でクラステンプレートを定義できます.
	template <テンプレート引数宣言> class name {
		・・・・・
	};
		
  ただし,コンストラクタ,デストラクタ,及び,メンバー関数の本体をクラス定義の外側に記述する場合は,関数テンプレートによって記述する必要があります.したがって,以下に示すような記述方法になります.
			// コンストラクタ(デストラクタも同様)
	template <テンプレート引数宣言> クラス名 <テンプレート引数> :: クラス名 (引数)
	{
		・・・・・
	};
			// メンバー関数
	template <テンプレート引数宣言> クラス名 <テンプレート引数> :: メンバー関数名 (引数)
	{
		・・・・・
	};
		
  次の例では,クラステンプレートを使用して,任意のサイズ(この例では,3 )の様々な型の 1 次元配列を確保するクラスを定義しています.メンバー関数 input をクラス定義内で記述し,コンストラクタとメンバー関数 print はクラスの外で記述しています.

(プログラム例14.2) クラステンプレート
/******************************/
/* クラステンプレート         */
/*      coded by Y.Suganuma   */
/******************************/
#include <iostream>
using namespace std;

/******************/
/* クラスTpの定義 */
/******************/
template <class Tp, int size> class Vector {
		Tp *p;
	public:
		Vector();   // コンストラクタ
		void print(int);   // 出力
		void input()   // 入力
		{
			int i1;
			for (i1 = 0; i1 < size; i1++) {
				cout << "   " << i1+1 << " 番目の要素は? ";
				cin >> p[i1];
			}
		}
};

/******************/
/* コンストラクタ */
/******************/
template <class Tp, int size> Vector <Tp, size>::Vector()
{
	p  = new Tp [size];
}

/*****************************/
/* データの出力              */
/*      k : 出力する要素番号 */
/*****************************/
template <class Tp, int size> void Vector <Tp, size>::print(int k)
{
	if (k < 0 || k > size-1)
		cout << "   要素番号が不適当です\n";
	else
		cout << "   " << (k+2) << " 番目の要素は " << p[k] << endl;
}

/************/
/* main関数 */
/************/
int main()
{
	int k = 0, sw;

	cout << "整数(0) or 実数(1) ? ";
	cin >> sw;
/*
	 整数
*/
	if (sw == 0) {
		Vector < int, 3 > iv;
		cout << "整数ベクトル\n";
		iv.input();
		while (k >= 0) {
			cout << "要素番号は ";
			cin >> k;
			iv.print(k);
		}
	}
/*
	 実数
*/
	else {
		Vector < double, 3 > dv;
		cout << "実数ベクトル\n";
		dv.input();
		while (k >= 0) {
			cout << "要素番号は ";
			cin >> k;
			dv.print(k);
		}
	}

	return 0;
}
		
14.3 C++ 標準ライブラリと STL ( Standard Template Library )

  C に printf,sqrt など,多くの標準関数が存在するように,C++ にもクラスを使用した多くの標準ライブラリが存在します.その内,STL ( Standard Template Library ) は,テンプレートの機能を利用した要素の集まりであり,コンテナ,イテレータ,アルゴリズム,関数オブジェクトから構成されています.なお,以下に示す表では,STL 以外の標準ライブラリについても,コンテナの中に入れてあります.今まで使用してきた cin や cout も標準ライブラリの一種ですが,これらについては,入出力クラスを参照してください.また,コンテナ及びアルゴリズムの使用例については,付録を参照してください.

14.3.1 コンテナ

 オブジェクトを入れるためのデータ構造です.配列もコンテナの一種といえますが,その構造は固定されています.また,領域の確保や解放についても,自分で責任を持って行わなければなりません.C++ には,動的に領域の確保や解放を行ってくれる様々な構造を持ったコンテナが用意されています.

vector 動的配列を保持します.C の配列と互換性を持ち,データ領域の連続性が保証されています.通常の動的配列の場合は,宣言時に大きさを宣言しなければなりませんが,vector の場合は必要ありません.また,要素数を途中で変更することも可能です.( STL )
deque 双方向の待ち行列に対応します.vector と似ていますが,動的配列の前にも要素の挿入や削除を行えるメンバー関数が存在する点が,vector とは異なっています.( STL )
set 要素を自動的にソートして保持し,要素自身をキーとした探索が可能です.map と似ていますが,map ではキーとデータを別々に保持します.( STL )
multiset 同じ値を持つ要素許す set です.( STL )
map 連想配列,つまり,添え字として数値以外(キー)を使用してデータを参照できる配列を保持します.set と同様,データは自動的にソートされます.( STL )
multimap 同じキーを持つデータ(キーの重複)を許す map です.( STL )
list 双方向リストを保持します.( STL )
queue 待ち行列を保持します.STL コンテナの一部を利用して,特別な機能を持たせたクラスであり,コンテナアダプタと呼ばれています.
priority_queue 優先順位付き待ち行列を保持します.要素を追加すると,優先順位に従いソートされます.STL コンテナの一部を利用して,特別な機能を持たせたクラスであり,コンテナアダプタと呼ばれています.
stack スタックを保持します.STL コンテナの一部を利用して,特別な機能を持たせたクラスであり,コンテナアダプタと呼ばれています.
bitset ビットの並びを保持します.
pair 2 つのデータをペアにして扱うためのコンテナです.
string 文字列を保持するクラスです.

14.3.2 イテレータ(反復子)

  通常の配列であれば,添え字やポインタによって簡単に各要素を参照できます.しかし,コンテナのように,その構造が複雑な場合は,それほど簡単ではありません.そこで,それを簡単にしたものがイテレータです.たとえば,it をイテレータとすると,ポインタと同じように,it++ で次の要素へ移動でき,また,*it によってその内容を参照できます.イテレータには,次の表に示すようなものが存在します.なお,イテレータを使用するためには,<iterator> ヘッダが必要です.

ランダムアクセスイテレータ 各要素にランダムにアクセスでき,かつ,要素の参照と設定が可能
双方向イテレータ 前方及び後方に移動でき,かつ,要素の参照と設定が可能
前方イテレータ 前方だけに移動でき,かつ,要素の参照と設定が可能
入力イテレータ 前方だけに移動でき,かつ,要素の設定が可能
出力イテレータ 前方だけに移動でき,かつ,要素の参照が可能

  各イテレータに対して様々な演算子を使用することができます.たとえば,it をある要素を指すイテレータとしたとき,「 it++ 」という演算によって,it は次の要素を指すことになります.ただし,すべてのイテレータにおいて同じ演算子を使用できるわけではありません.たとえば,「 -- 」という演算は,双方向またはランダムアクセスイテレータだけでしか使用できません.また,「 it += 2 」のような演算は,ランダムアクセスイテレータだけで使用できます.各イテレータにおいて使用できる演算子は以下に示すとおりです.

ランダムアクセスイテレータ *, ->, =, +, -, ++, --, [], <, >, <=, >=, -=, +=, ==, !=
双方向イテレータ *, ->, =, ++, --, ==, !=
前方イテレータ *, ->, =, ++, ==, !=
入力イテレータ *, ->, =, ++, ==, !=
出力イテレータ *, =, ++

  STL の各コンテナには,コンテナ固有のイテレータが定義されています.また,逆方向イテレータも定義されています.以下に示すのは,vector に対してイテレータを使用した例です.

(プログラム例 14.3 ) イテレータ
#include <stdio.h>
#include <vector>
using namespace std;

int main()
{
	vector<int> v;
	vector<int>::iterator it;   // イテレータ
	vector<int>::reverse_iterator r_it;   // 逆方向イテレータ
					// 初期設定
	printf("**初期設定**\n");
	v.push_back(0);
	v.push_back(4);
	v.push_back(3);
	v.push_back(1);
	v.push_back(2);
	for (it = v.begin(); it != v.end(); it++)
		printf("  %d", *it);
	printf("\n**後ろの 3 つを出力**\n");
	it = v.begin() + 2;   // vector の場合,ランダムアクセスイテレータであるため可能
	while (it != v.end()) {
		printf("  %d", *it);
		it++;
	}
	printf("\n**逆方向に出力**\n");
	for (r_it = v.rbegin(); r_it != v.rend(); r_it++)
		printf("  %d", *r_it);

	return 0;
}

(出力)

**初期設定**
  0  4  3  1  2
**後ろの 3 つを出力**
  3  1  2
**逆方向に出力**
  2  1  3  4  0
		
  C++ には,イテレータを目的に合わせて変換するアダプタも用意されています.その一つが,要素を挿入するときに使用する挿入イテレータです.

insert_iterator コンテナに要素を挿入する出力イテレータ.insert_iterator を生成する inserter() 関数を持っています.
front_insert_iterator コンテナの最初に要素を挿入する出力イテレータ.push_front() を使用して実行するため,このメンバー関数を持つコンテナだけに適用できます.front_insert_iterator を生成する front_inserter() 関数を持っています.
back_insert_iterator コンテナの最後に要素を挿入する出力イテレータ.push_back() を使用して実行するため,このメンバー関数を持つコンテナだけに適用できます.back_insert_iterator を生成する back_inserter() 関数を持っています.

(プログラム例 14.4 ) 挿入イテレータ
#include <stdio.h>
#include <deque>
using namespace std;

int main()
{
	int i1;
	deque<int> q1, q2, q3;
	deque<int>::iterator it;
					// 初期設定
	printf("**初期設定 : q1**\n");
	for (i1 = 0; i1 < 5; i1++)
		q1.push_back(i1);
	for (it = q1.begin(); it != q1.end(); it++)
		printf("  %d", *it);
	printf("\n");
	printf("**初期設定 : q2**\n");
	for (i1 = 0; i1 < 3; i1++)
		q2.push_back(10 * (i1 + 1));
	for (it = q2.begin(); it != q2.end(); it++)
		printf("  %d", *it);
	printf("\n");
					// insert_iterator
	printf("**2 番目の要素の前に 5 を挿入( insert_iterator )**\n");
	it = q1.begin() + 1;
	insert_iterator<deque<int> > i_it(q1, it);
	*i_it = 5;
	for (it = q1.begin(); it != q1.end(); it++)
		printf("  %d", *it);
	printf("\n");
					// inserter
	printf("**q1 の 3 番目の要素の前に q2 を挿入( inserter )**\n");
	copy(q2.begin(), q2.end(), inserter(q1, q1.begin()+2));
	for (it = q1.begin(); it != q1.end(); it++)
		printf("  %d", *it);
	printf("\n");
					// front_insert_iterator, back_insert_iterator
	printf("**最初と最後に 20 と 10 を挿入**\n");
	printf("**( front_insert_iterator と back_insert_iterator )**\n");
	front_insert_iterator<deque<int> > f_it(q1);
	*f_it = 20;
	back_insert_iterator<deque<int> > b_it(q1);
	*b_it = 10;
	for (it = q1.begin(); it != q1.end(); it++)
		printf("  %d", *it);
	printf("\n");
					// back_inserter
	printf("**q2 を q3 にコピー( back_inserter )**\n");
	copy(q2.begin(), q2.end(), back_inserter(q3));
//	copy(q2.begin(), q2.end(), q3.begin());   // 挿入タイプでないとだめ
	for (it = q3.begin(); it != q3.end(); it++)
		printf("  %d", *it);
	printf("\n");
					// copy
	printf("**q2 を q1 に上書きコピー**\n");
	copy(q2.begin(), q2.end(), q1.begin());   // 上書きコピー
	for (it = q1.begin(); it != q1.end(); it++)
		printf("  %d", *it);
	printf("\n");

	return 0;
}

(出力)

**初期設定 : q1**
  0  1  2  3  4
**初期設定 : q2**
  10  20  30
**2 番目の要素の前に 5 を挿入( insert_iterator )**
  0  5  1  2  3  4
**q1 の 3 番目の要素の前に q2 を挿入( inserter )**
  0  5  10  20  30  1  2  3  4
**最初と最後に 20 と 10 を挿入**
**( front_insert_iterator と back_insert_iterator )**
  20  0  5  10  20  30  1  2  3  4  10
**q2 を q3 にコピー( back_inserter )**
  10  20  30
**q2 を q1 に上書きコピー**
  10  20  30  10  20  30  1  2  3  4  10
		
  入出力ストリームをイテレータで扱うこともできます.それを行うのがストリームイテレータであり,入力ストリームを対象とした istream_iterator,出力ストリームを対象とした ostream_iterator などがあります.

(プログラム例 14.5 ) ストリームイテレータ
#include <iostream>
#include <iterator>
#include <vector>
using namespace std;

int main()
{
	vector<int> v;
	vector<int>::iterator it;
	istream_iterator<int> i_it(cin);
	ostream_iterator<int> o_it(cout);

	*i_it;
	while (*i_it != 0) {
		v.push_back(*i_it);
		i_it++;
	}
	for (it = v.begin(); it != v.end(); it++) {
		cout << "  ";
		*o_it = *it;
		o_it++;
	}

	return 0;
}

(出力) 10 20 30 0 を入力したとき

  10  20  30
		

14.3.3 アルゴリズム( STL )

  アルゴリズムは,関数テンプレートの一種です.各コンテナには様々なメンバー関数が存在しますが,アルゴリズムとの違いは,アルゴリズムでは異なるコンテナを扱えるといった点です.その主ものは以下に示すとおりです.なお,アルゴリズムを使用するためには,<algorithm> ヘッダが必要です.

シーケンス変更なし adjacent_find() 指定した範囲で,同じ要素が連続している位置,または,連続する 2 つの要素が二項関数オブジェクトで指定された関係にある位置を探します.
count() 指定した範囲で,指定した値と一致する要素の数を返します.
count_if() 指定した範囲で,単項関数オブジェクトが真となる要素の数を返します.
equal() first1 から last1 で指定した範囲が,first2 で指定された要素列と等しい,または,二項関数オブジェクトで指定した意味で等しい場合に true を返します.
find() 指定した範囲で,指定した要素を探します.
find_end() first1 から last1 で指定した範囲で,first2 から last2 で指定された要素列,または,二項関数オブジェクトで指定した意味で等しい要素列が最後に表れた位置を返します.
find_first_of() first1 から last1 で指定した範囲で,first2 から last2 に含まれるいずれかの要素,または,二項関数オブジェクトで指定した意味で等しいいずれかの要素が最初に表れた位置を返します.
find_if() 指定した範囲で,単項関数オブジェクトが真になる要素を探します.
for_each() 指定した範囲に,指定した関数による処理を適用します.
mismatch() first1 から last1 で指定した範囲が,first2 で指定された要素列と異なる,または,二項関数オブジェクトで指定した意味で異なる位置を返します.
search() first1 から last1 で指定した範囲で,first2 から last2 で指定された要素列,または,二項関数オブジェクトで指定した意味で等しい要素列が最初に表れた位置を返します.
search_n() first1 から last1 で指定した範囲で,指定された要素の n 個の要素列,または,二項関数オブジェクトで指定した意味で指定した要素と等しい n 個の要素列が最初に表れた位置を返します.
シーケンス変更あり copy() 指定した範囲をコピーします.
copy_backward() 指定した範囲を後方からコピーします.
fill() 指定した範囲の要素を,指定した要素に設定します.
fill_n() 指定した位置から始まる指定した数の要素を,指定した要素に設定します.
generate() 指定した範囲の要素を,指定した関数によって生成された要素に設定します.
generate_n()  指定した位置から始まる指定した数の要素を,指定した関数によって生成された要素に設定します.
iter_swap() イテレータで指定した 2 つの要素を交換します.
partition() 単項述語オブジェクトが真になる要素を偽になる要素の前に移動します.その際,最初の前後関係が維持されるとは限りません.
random_shuffle() イテレータで指定された範囲の要素をランダムに並び替えます.乱数発生関数を指定することも可能です.
remove() 指定した範囲の指定した要素を削除します.ただし,コンテナのサイズ自身は変化しません.
remove_copy() 指定した範囲の指定した要素を削除し,指定した場所にコピーます.ただし,コピー元の要素は変化しません.
remove_copy_if() 指定した範囲の単項関数オブジェクトが真になる要素を削除し,指定した場所にコピーます.ただし,コピー元の要素は変化しません.
remove_if() 指定した範囲の単項関数オブジェクトが真になる要素を削除します.ただし,コンテナのサイズ自身は変化しません.
replace() 指定した範囲の指定した要素を,指定した要素に置き換えます.
replace_copy() 指定した範囲の指定した要素を,指定した要素に置き換え,指定した場所にコピーします.ただし,コピー元の要素は変化しません.
replace_copy_if() 指定した範囲の単項関数オブジェクトが真になる要素を,指定した要素に置き換え,指定した場所にコピーます.ただし,コピー元の要素は変化しません.
replace_if() 指定した範囲の単項関数オブジェクトが真になる要素を,指定した要素に置き換えます.
reverse() 指定した範囲の要素の並び順を反転します.
reverse_copy() 指定した範囲の要素の並び順を反転し,指定した場所にコピーします.ただし,コピー元は変化しません.
rotate() イテレータ middle が指す要素が先頭になるように左方向に回転します.
rotate_copy() イテレータ middle が指す要素が先頭になるように左方向に回転し,指定した場所にコピーします.ただし,コピー元は変化しません.
stable_partition() 単項述語オブジェクトが真になる要素を偽になる要素の前に移動します.その際,最初の前後関係が維持されます.
swap() 指定した 2 つの要素を交換します.
swap_ranges() イテレータ first1,last1 で指定した範囲を first2 で始まる範囲と交換します.
transform() イテレータで指定された範囲の要素に対して,1 引数関数,または,2 引数関数の結果を適用し,指定した場所に保存します.
unique() 指定した範囲の指定した重複した要素,または,二項関数オブジェクトを真にする意味で重複した要素を削除します.ただし,コンテナのサイズ自身は変化しません.
unique_copy() 指定した範囲の指定した重複した要素,または,二項関数オブジェクトを真にする意味で重複した要素を削除し,指定した位置にコピーします.ただし,コピー元は変化しません.
ソート関係 binary_search() ソート済みの系列に対して,指定した範囲で,指定した要素を探します.2 引数の比較関数を指定することも可能です.
equal_range() ソート済みの系列に対して,指定した要素を系列の順序を破壊することなく挿入できる位置( lower_bound と upper_bound )を返します.2 引数の比較関数を指定することも可能です.
includes() 最初の系列に,後ろの系列のすべての要素が含含まれているとき true を返します.2 引数の比較関数を指定することも可能です.
inplace_merge() 同じ系列内の first から middle で指定した部分と,middle から last で指定した部分をマージします.2 引数の比較関数を指定することも可能です.
lexicographical_compare() 2 つの系列をアルファベット順に比較し,先の系列が次の系列より辞書的に前にあれば true を返します.2 引数の比較関数を指定することも可能です.
lower_bound() ソート済みの系列に対して,指定した要素以上の値を持つ最初の要素を返します.2 引数の比較関数を指定することも可能です.
make_heap() ヒープを構築します.ヒープとは,系列の中の最大要素をルートとした木構造です.2 引数の比較関数を指定することも可能です.
max() 2 つの値の内,大きい方の値を返します.2 引数の比較関数を指定することも可能です.
max_element() 指定した範囲における最大値を返します.2 引数の比較関数を指定することも可能です.
merge() 2 つの領域をマージします.2 引数の比較関数を指定することも可能です.
min() 2 つの値の内,小さい方の値を返します.2 引数の比較関数を指定することも可能です.
min_element() 指定した範囲における最小値を返します.2 引数の比較関数を指定することも可能です.
next_permutation() 系列の各要素を辞書的順序で並べた場合において,現在の並べ方の次になる並べ方を返します.次の並べ方が存在しない場合,辞書的順序における最初の並べ方に並べ替えます.2 引数の比較関数を指定することも可能です.
nth_element() first から last によって指定した範囲を対象とし,nth で指定した要素より小さい要素をその前に,大きい要素をその後ろに移動します.2 引数の比較関数を指定することも可能です.
partial_sort() first から last で,指定した範囲をソートします.その際,ソートされるのは first から middle までであり,それ以降はばらばらとなります.2 引数の比較関数を指定することも可能です.
partial_sort_copy() first から last で,指定した範囲をソートし,result_first から result_last の間に可能な限りコピーします.2 引数の比較関数を指定することも可能です.
pop_heap() ヒープの先頭の要素(最大の要素)を系列の最後に移動し,ヒープを再構築します.ヒープとは,系列の中の最大要素をルートとした木構造です.2 引数の比較関数を指定することも可能です.
prev_permutation() 系列の各要素を辞書的順序で並べた場合において,現在の並べ方の前になる並べ方を返します.前の並べ方が存在しない場合,辞書的順序における最後の並べ方に並べ替えます.2 引数の比較関数を指定することも可能です.
push_heap() ヒープに要素を追加し,ヒープを再構築します.ヒープとは,系列の中の最大要素をルートとした木構造です.2 引数の比較関数を指定することも可能です.
set_difference() 系列 1 に含まれ,系列 2 に含まれない要素の集合を出力します.なお,2 つの系列は前もってソートしておく必要があります.2 引数の比較関数を指定することも可能です.
set_intersection() 系列 1 ,及び,系列 2 の両方に含まれる要素の集合を出力します.なお,2 つの系列は前もってソートしておく必要があります.2 引数の比較関数を指定することも可能です.
set_symmetric_difference() 系列 1 ,または,系列 2 に含まれない要素の集合を出力します.なお,2 つの系列は前もってソートしておく必要があります.2 引数の比較関数を指定することも可能です.
set_union() 系列 1 ,または,系列 2 に含まれる要素の集合を出力します.なお,2 つの系列は前もってソートしておく必要があります.2 引数の比較関数を指定することも可能です.
sort() 指定した範囲をソートします.比較関数を指定することも可能です.
sort_heap() ヒープをソートします.ヒープとは,系列の中の最大要素をルートとした木構造です.2 引数の比較関数を指定することも可能です.
stable_sort() 指定した範囲をソートします.その際,等しい要素の並び順が保持されます.2 引数の比較関数を指定することも可能です.
upper_bound() ソート済みの系列に対して,指定した要素より大きい値を持つ最初の要素を返します.2 引数の比較関数を指定することも可能です.
数値関係 accumulate() イテレータで指定された範囲の総和を計算します.2 引数関数によって,和の計算方法を指定することもできます.
adjacent_difference() 隣接する要素間の差からなる系列を計算します.2 引数関数によって,差の計算方法を指定することもできます.
inner_product() イテレータで指定された範囲で,2 つのコンテナ v1 と v2 の内積を計算します.2 引数関数によって,和及び積の計算方法を指定することもできます.
partial_sum() イテレータで指定された範囲の部分和(順番に和を求めながら,その段階までの和をその要素の値とする)を計算します.2 引数関数によって,和の計算方法を指定することもできます.

14.3.4 関数オブジェクト

  関数オブジェクトは,関数呼び出し演算子( "()" )をオーバーロードしたものです.オブジェクトですが,関数のように使用することが可能です.STL の多くの場所で使用されています.以下に示すのは,map で greater と less を使用した例です.なお,関数オブジェクトを使用するためには,<functional> ヘッダが必要です.

(プログラム例 14.6 ) map における関数オブジェクトの使用
#include <iostream>
#include <map>
using namespace std;

int main()
{
	map<string, int, greater<string> > m1;
	map<string, int, less<string> > m2;
	map<string, int, greater<string> >::iterator g_it;
	map<string, int, less<string> >::iterator l_it;

	cout << "**コンテナ m1 (greater)**\n";
	m1.insert(pair<string, int>("suzuki", 40));
	m1.insert(pair<string, int>("sato", 60));
	m1.insert(pair<string, int>("yamada", 70));
	m1.insert(pair<string, int>("tanaka", 50));
	m1.insert(pair<string, int>("kato", 90));
	for (g_it = m1.begin(); g_it != m1.end(); g_it++)
		cout << "  " << (*g_it).first << " " << (*g_it).second;
	cout << endl;

	cout << "**コンテナ m2 (less)**\n";
	m2.insert(pair<string, int>("suzuki", 40));
	m2.insert(pair<string, int>("sato", 60));
	m2.insert(pair<string, int>("yamada", 70));
	m2.insert(pair<string, int>("tanaka", 50));
	m2.insert(pair<string, int>("kato", 90));
	for (l_it = m2.begin(); l_it != m2.end(); l_it++)
		cout << "  " << (*l_it).first << " " << (*l_it).second;
	cout << endl;

	return 0;
}

(出力)

**コンテナ m1 (greater)**
  yamada 70  tanaka 50  suzuki 40  sato 60  kato 90
**コンテナ m2 (less)**
  kato 90  sato 60  suzuki 40  tanaka 50  yamada 70
		

  関数オブジェクトには,1 つの引数をとる単項関数オブジェクトと,2 つの引数をとる二項関数オブジェクトがあります.C++ に組み込まれている関数オブジェクトには以下に示すようなものがあります.

単項関数オブジェクト logical_not, negate
二項関数オブジェクト plus, minus, multiplies, divides, modulus, equal_to, not_equal_to, greater, greater_equal, less, less_equal, logical_and, logical_or

  関数オブジェクトは自分で作成することもできます.STL の中で使用するには,下に示す例で示すように,unary_function や binary_function を継承した方が作りやすいと思います.なお,以下の例では,二項関数オブジェクトは STL 以外の場所で使用しています.また,単項関数オブジェクトは,クラステンプレートで記述してあります.

(プログラム例 14.7 ) 関数オブジェクトの作成
#include <iostream>
#include <list>
#include <functional>
using namespace std;
					// 単項関数オブジェクト
template <class T> class is_odd : public unary_function<T, bool>
{
	public:
		bool operator() (T k)
		{
			return (bool)(k % 2);
		}
};
					// 二項関数オブジェクト
class Equal : public binary_function<int, int, bool>
{
	public:
		result_type operator() (first_argument_type a, second_argument_type b)
		{
			return (result_type)(a == b);
		}
};

int main()
{
	list<int> ls;
	list<int>::iterator it;
					// 初期設定
	printf("**初期設定**\n");
	ls.push_back(0);
	ls.push_back(2);
	ls.push_back(3);
	ls.push_back(4);
	ls.push_back(1);
	for (it = ls.begin(); it != ls.end(); it++)
		cout << "  " << *it;
	cout << endl;
					// 奇数である要素を削除
	Equal eq;
//	equal_to<int> eq;   // 組み込まれている equal_to を使用する場合
	printf("**奇数である要素を削除**\n");
	ls.remove_if(is_odd<int>());
	for (it = ls.begin(); it != ls.end(); it++) {
		if (eq(*it, 2))
			cout << "  " << *it << " は 2 に等しい" << endl;
		else
			cout << "  " << *it << " は 2 に等しくない" << endl;
	}

	return 0;
}

(出力)

**初期設定**
  0  2  3  4  1
**奇数である要素を削除**
  0 は 2 に等しくない
  2 は 2 に等しい
  4 は 2 に等しくない
		

演習問題14

[問1]整数( int ),実数( double ),及び,文字列(クラスとして定義)の加算を行う関数テンプレート plus と,それらの結果の出力を行う関数 print を定義せよ.ただし,文字列の加算とは,文字列の連結を意味するものとする.

[問2] n 個の任意の型のデータをディスクから配列に読み込み,ソートして配列に保存するクラステンプレートを作成せよ.

ホームページ 目次 演習解答例目次 付録目次 索引