テンプレートとは、テンプレートの活用方法、STLもさらっと触れてみる話 —勉強しなおしの記録というか学習ノート-17日目-

Unreal Engin4すごい!(語彙力)ってなったので、少しいじりつつ、むかーしかじった事あるC,C++の学びなおしの学習ノート。この連載記事の詳しい趣旨と注意事項は1日目をご覧ください。

目次

使い勝手の良い部品を作る –オブジェクト指向でより良いプログラムを書くために

プログラミング ソースコード パソコン 画面

『オブジェクト指向プログラミング』では、”クラス”にしても”関数”にしても、なるべく広い目的で転用・使いまわしが効くような、優れた部品を作ることが、良いプログラムを書く上で求められるのです。

例えば、『ジェネリックプログラミング』という手法で、

具体的なデータ型に直接依存しない、抽象的かつ汎用的なコード記述を可能にする

引用元:ジェネリックプログラミング – Wikipedia

という考え方があるのですが、そう言った、なるべく「汎用性の高い部品」を作っておいて、適度にコーディングの行数を少なくする、ためのC++言語での便利な方法の1つに、『テンプレート』というものがあります。

テンプレート

大量生産 生産ライン マシーン 工場

ざっくり言ってしまうと、『テンプレート』は、定義した関数やクラスの「“型”への対応を『コンパイラ』さんにお任せする」というようなことをできる機能です。(『コンパイラ』さん毎度いつもお世話になってますm(_ _)m)

コンパイラ プログラム コンパイル 擬人化

一度定義しておけば、”データ型”によって、『コンパイラ』さんが自動的に指定した通りその”データ型”用の関数やクラスに変換してくれます。また、テンプレートを、各”データ型”専用に変換する工程のことを『インスタンシエート』と言ったりもするようです。

同じような処理内容で扱う”型”が違う場合、「”型”だけを書き換えた同じようなクラスや関数をいくつも記述」、またはクラスでなく一行で済むような関数であれば「関数形式マクロで定義しておく」というような方法も思い浮かぶかと思います。

ですが、前者の場合、プログラム全体の行数が増え、可読性が低くなったり、後者の場合は、型の強制キャストが起こってしまう可能性があるため、エラーを出さずに予想外の動作をする可能性があったりします。マクロの型の強制キャストについては他所サイト様に解説丸投げします。

外部ページ:ロベールのC++教室 – 第31章 治金工場 –

『テンプレート』では、同じ処理の”型”が違うだけの関数やクラスを必要に応じ、オーバーロード的に半自動生成してくれます。

なので、記述したソースコードはスッキリしますし、マクロのような型の強制キャストにも悩まされることはありません。

※ただし、あまりテンプレートを乱用すると、『コンパイラ』さんが変換した後に生成されるコードが大きくなりますので、乱用はおすすめできません。

『テンプレート』には、定義するものが”関数”ならば『テンプレート関数』、”クラス”ならば『テンプレートクラス』と言った風に大きく分けて2種類が存在します。

テンプレート関数

定義

template <typename 仮の型名> 
   仮の型名 関数名(仮の型名 引数1, 仮の型名 引数2){
       // 処理
   }     

複数の”型”を指定したい場合は↓のような感じ。

template <typename 仮の型名1, typename 仮の型名2> 
   仮の型名 関数名(仮の型名1 引数1, 仮の型名2 引数2){
       // 処理
   }  

使い方

関数名<型>(引数1, 引数2);

また、引数が両方とも”同じ型”であり、関数の”型”もそのままでいい場合は”<型>”部分を省略できます。

関数名(引数1, 引数2);

仮の型名に指定された型がなんであれ、引数1と引数2で(場合によってはキャストして)、定義した処理を行えます。

また複数の型を定義していた場合は、↓のようになります。

関数名<型1, 型2>(引数1, 引数2);

実際の使用例

#include <iostream>

using namespace std;

//テンプレート関数
template <typename T>
T average(T x,T y){
	return (x + y) / 2;
}

int main() {
	cout << average(2, 8) << endl;
	cout << average<int>(16, 205.6) << endl;
	cout << average<double>(100, 3.14) << endl;
	return 0;
}
  • 実行結果
5
110
51.57

いくつもの「型だけ違う関数」を記述するよりも、これ一つで済むのなら、プログラムも行数少なく出来てスッキリしますし、こちらの方が便利に使えていいですよね!

  • ちなみに、『テンプレート』も『オーバーロード』が可能
#include <iostream>

using namespace std;

//テンプレート関数
template <typename T>
T mul(T x, T y) {
	return x * y;
}
template <typename T>
T mul(T x) {
	return x * x;
}

int main() {
	cout << mul(2, 8) << endl;
	cout << mul<int>(16, 3.14) << endl;
	cout << mul<double>(42.195) << endl;
	return 0;
}

実行結果↓

16
48
1780.42

テンプレートクラス

『テンプレートクラス』の場合、『テンプレート関数』と違って注意することがあります。テンプレート宣言をいつものように、ファイル分けで宣言と定義を、ヘッダファイルとソースファイルで分けるのではなく、 ヘッダファイル(.h)一つで記述することが推奨されるという点です。

宣言と定義

template<typename 仮の型名> class クラス名{
private:
     /*略*/
public:
     /*略*/
};

メンバ部分の宣言は通常とあまり変わりません。ただ、型が”仮の型名”のところで宣言したものと同じなら、関数の時と同じように”仮の型名”で定義しておけます。

使い方

クラス名<型>インスタンス名;

こちらも、通常の時と違うのは”<型>”が入っているという点だけ。

実際の使用例

  • encryption.h(テンプレートクラス)
#pragma once
/********************
* 暗号テンプレートクラス
*/
template<typename T> class Encryption {
private:
	T account;
	T keyword;
public:
	Encryption(const T _account, const T _keyword) { // アカウントとキーワードの初期化
		account = _account;
		keyword = _keyword;
	}
	T get_account() const {// アカウントの値ゲット
		return account;
	}
	T get_keyword() const {// アカウントの値ゲット
		return keyword;
	}
	bool collation(T _account, T _keyword) { // アカウントとキーワード照合
		return ((account == _account) && (keyword == _keyword));
	}
};
  • main.cpp
#include "encryption.h"
#include <iostream>
#include <string>

using namespace std;

int main() {
	string a, b;
	Encryption<int>enc1(14, 533677);
	Encryption<string>enc2("abcdef", "xyz987");
	cout << "アカウント:"<<enc1.get_account() << ", キーワード:" << enc1.get_keyword() << endl;
	cout << "アカウント名を入力して下さい:";
	cin >> a;
	cout << "キーワードを入力して下さい:";
	cin >> b;
	if (enc2.collation(a, b)) cout << "一致しました。" << endl;
	else cout << "両方かどちらかが間違っているようです。" << endl;
	
	return 0;
}
  • 実行結果
アカウント:14, キーワード:533677
アカウント名を入力して下さい:abcdef
キーワードを入力して下さい:xyz987
一致しました。

STL

ライブラリ 図書館 レンガ造り

STL(Standard Template Library)とは、C++の標準ライブラリで定義済みのテンプレートです。

と呼ばれる各要素からなっています。 その中のごく一部を抜き出して書いてみます。

コンテナ

オブジェクトを保持するデータ構造。

名前説明
vector動的配列
array(*)固定長配列
list双方向リスト
set集合
map連想配列(実装についてはsetと同じ)

コンテナアダプタ

『コンテナ』とセットで使う。他の『コンテナ』を内部に保持して、その『コンテナ』の一部機能のみを引き出すための、特殊な機能を持つもの。

名前説明
stackスタック(FILO; First In, Last Out)
queueキュー(FIFO; First In, FIrst Out)

イテレータ

『コンテナ』のような要素集合体の個々の要素を参照するための”クラス”。「反復子」や「アイテレータ」とも呼ばれる。

『イテレータ』は配列の要素を指す”ポインタ”を抽象化したものと言えるが、すべてのイテレータがポインタの機能全てを保持しているわけではない。

名前説明
instream_iterator入力ストリームイテレータ
ostream_iterator出力ストリームイテレータ

アルゴリズム

『イテレータ』で指定された『コンテナ』の要素に対して特定の操作を行うための”関数”。一般的に言う”アルゴリズム”とは別。

名前説明
all_of全ての要素が条件をみたしているかを調べる
for_each全ての要素に対して処理を行う
count指定された値である要素の数を数える
copy指定された範囲の要素をコピーする
transform全ての要素に関数を適用する
remove指定された要素を退ける
reverse要素の並びを逆にする
sort範囲を並べ替える
max最大値を取得する
min最小値を取得する
clamp値を範囲内に収める

関数オブジェクト

上の項で取り扱ったので省略。

読み飛ばしても問題ない自分語りしてるあとがき

最初から後々の事を考えた設計するのってすごく難しいので、 今回冒頭で 『使い勝手の良い部品を作る –オブジェクト指向でより良いプログラムを書くために 』なんて偉そうな名前つけた項から入りましたが、私はまだ、最初に「勢いでコード書いて」、後から

「誰がこんなごちゃごちゃしたプログラム書いたんだ!(過去の自分)」

という具合に、上手く「汎用性高い部品」を最初から綺麗に作れず、結局「コード書き直し二度手間」を踏むという事をやらかしがちな、へぼプログラマだったりします(´д`;)

これは私が思うに(またまた偉そうなこと言いますが)「どれくらいコード書いたか」や、「どれくらい良いコードを見たか」という、『経験』が大事になってくると思います。
(はい、まだ全然修行中ですorz)

なので、「先駆者様方のコードを覗きに行かせて頂く」のって、大切なんですよね。

綺麗なソースコードはある種芸術品だと思ってたりしますので、見てて楽しいですし。

いつかそういった綺麗なソースコードが当たり前に書けるようになりたいのですが、そう一朝一夕に行かないのがなんとも歯がゆいものですね。日々精進です。

ご意見・ご感想・ご質問、また、ここ間違ってるよとか、もっといい方法あるよといったご指摘などございましたら、お手数ですがコメント欄やtwitterアカウントほろほろり(@_horo_horori)へお願いしますm(_ _)m

参考にさせていただいたサイト・ページ一覧

Pocket

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください