C++入門
シープラスプラスにゅうもん

PCinfo トップページプログラミング専門分野ハードウエアその他

テンプレート関数

今回はテンプレート関数(汎用関数)の話をしましょう。

テンプレート関数とは、引数や戻り値の型に依存することなく使用できる関数のことをいいます。例えば 何らかの自作関数を作るときに、処理内容は同じであるにも関わらず引数のみが違うということがあったとします。 その場合は関数をオーバーロードしなければならない事を以前お話したと思います。

∇忘れた人は関数のオーバーロードを復習しよう!

ですが、処理内容が同一であるにも関わらず似たような関数をいくつも記述するのは面倒ですし、可読性の点 からいってもあまり好ましいことであるとは言えません。

次のプログラムは与えられた引数を2倍してその結果を表示するというものです。
まずは関数のオーバーロードによる記述を見てください。

#include <iostream>
using namespace std;

void myfunc(int data)
{
	cout << data * 2 << endl;
}

void myfunc(long data)
{
	cout << data * 2 << endl;
}

void myfunc(float data)
{
	cout << data * 2 << endl;
}

int main(int, char**)
{
	int   a = 3;
	long  b = 5;
	float c = 0.3f;

	myfunc(a);
	myfunc(b);
	myfunc(c);

	return 0;
}

どうでしょうか。myfuncという3つの自作関数で唯一違っている点は引数の型のみです。であるにも関わらず、 同じような関数をいくつもだらだらと記述するのはあまり良いとは言えません。

上記の例では関数の中身がまだ1行程度ですからさほど面倒でもありませんが、これが数十行になろうもんなら 非常に見苦しく煩わしいものに間違いなく化けます。

そこで、もう少しシンプルな記述方法は無いものかという要請を受けて登場したのがテンプレートという 仕組みなのです。テンプレートを使うと引数・戻り値の型を意識しないで関数を自作することができます。

では実際に上記のプログラムをテンプレート関数を使ったプログラムに書き直してみましょう。

#include <iostream>
using namespace std;

template <class X>
void myfunc(X data)
{
	cout << data * 2 << endl;
}

int main(int, char**)
{
	int   a = 3;
	long  b = 5;
	float c = 0.3f;

	myfunc(a);
	myfunc(b);
	myfunc(c);

	return 0;
}

どうですか?ずいぶんシンプルになったでしょ?

見てだいたい想像が付くと思いますが、template <class X>と記述することによって X という 何にでも適用できる型(汎用型)を作ってしまうんです。あとはその型を引数や戻り値に用いることによって その型を意識しなくて済む訳です。上の例では引数にだけ適用してますが、戻り値にも適用することができます。


----------------------------------------------------------------
補足:

これで今回の話はおしまいにしてもいいんですが、少し内部的な話を補足しておきます。

テンプレート関数は自分で似たような関数をいくつも作る手間から解放してくれます。
言いかえると関数をいくつも作る作業をコンパイラにお任せしている訳です。

コンパイラはそれぞれのデータの型に合わせて、それ専用の関数を自動生成します。つまり簡単に説明すると テンプレート関数を使用したプログラム(下のプログラム)はコンパイラによって内部的には 関数をオーバーロードしたプログラム(上のプログラム)に置き換わっちゃう訳です。

ついでに専門用語も解説しちゃいますと、その置き換え作業のことをインスタンシエートと呼び、 その結果自動生成される関数のことを生成された関数と呼びます。

インスタンシエートという言葉から想像が付くように、生成された関数はテンプレート関数のインスタンスな 訳です。とすると汎用型の定義部分が <class X> となっている理由がなんとなく分りますよね!


目次へ







テンプレートクラス

名前から想像が付くと思いますが、テンプレートクラス(汎用クラス)とはテンプレート関数の 考えかたをクラスに適用したものです。

テンプレート関数同様に型に依存することなくサクサクとクラスが扱えたら便利な場合がありますので、 このような仕組みが生まれた訳です。テンプレート関数に比べると定義の仕方などがやや面倒くさい のですが、極力簡単に説明しますのでしっかりと形だけでも理解しましょう!

下のプログラムは前回の「テンプレート関数」のとこで登場したプログラムをテンプレートクラス風に アレンジしたものです。クラスの定義・メンバ関数の定義・オブジェクトの定義方法に注目しながら 見てください。

#include <iostream>
using namespace std;

///クラスの定義///
template <class X>
class MyClass{
	X data;
public:
	X myfunc(X);
};

///メンバ関数の定義///
template <class X>
X MyClass<X>::myfunc(X temp_data)
{
	data = temp_data * 2;
	return data;
}

int main(int, char**)
{
	MyClass<int>   ObjectA;//
	MyClass<long>  ObjectB;//オブジェクト作成
	MyClass<float> ObjectC;//

	int   a = 3;
	long  b = 5;
	float c = 0.3f;

	cout << ObjectA.myfunc(a) << endl;
	cout << ObjectB.myfunc(b) << endl;
	cout << ObjectC.myfunc(c) << endl;

	return 0;
}

今回はクラスの "メンバ変数" ・ メンバ関数の"引数" ・ "戻り値" 全てに汎用型を適用してみました。 汎用型は別にXである必要はありません。 S でも T でもOKです!ついでに言うと大文字1字である 必要もありません。

ですが、普通は通常の変数とごっちゃになるのを防ぐため大文字1字で表現する事が多いようです。 少なくともこれで通常の変数との競合は防ぐ事が出来ますので、汎用型は大文字1字での記述を心掛けましょう。


次にメンバ関数ですが、外部で定義する場合は面倒でも template <class X> と記述しなければ なりません。もちろん内部で定義する場合はその必要はありません。クラス定義のところで汎用型は 既に定義しているのですからわざわざメンバ関数のとこで2度目の定義をすのは冗長だと私は思うのですが 何故かせねばなりません。決まりごとです。

※もし理由をご存知でしたら教えてくださいm(__)m


では最後にメイン関数でオブジェクト(インスタンス)を作成している部分を見てください。

もう想像は付いてると思いますが、ここで初めてクラスでのXの型が指定されるわけです。 型の定義はクラス定義の段階で行うのではなく実体(オブジェクト)を作る段階で行う というのがテンプレートクラスの基本的な考え方と言えましょう。


今回はテンプレートクラスの作り方を知ってもらうのが目的でしたのでプログラムは簡単なものにしました。 ですが、テンプレートクラスを理解するためのプログラムとしては正直あまり良い例であるとは言えません。 そのおかげで「結局コレって何の役に立つの?」と首をかしげている方も多いと思われます。

まぁもう少しマシな例ならC++の入門本を見れば大抵載ってますんで、有用性に関してはそれで 調べてみてください。気が向いたらPCinfoでも扱います。


目次へ







フレンド関数

今回はフレンド関数のお話です。通常クラスの非公開メンバ(privateな関数や変数)には同一クラスの メンバ関数からしかアクセスが許されていません。しかし、時として非公開メンバに対し、メンバ関数以外の 関数でもってアクセスしたい場合も出てくる訳です。さりとて、非公開メンバをpublic宣言で公開メンバへ・・・ ってのは避けたい、という場合、フレンド関数を使うことでメンバ関数以外からの非公開メンバへのアクセス をすることが出来ます。

フレンド関数とは、一言でいうと「非公開メンバにアクセスするだけの関数」と言えます。作り方は簡単! フレンド関数としたい関数の先頭にfriendと付加するだけ!他のこまごまとしたルールはプログラムを眺めながら 解説しましょう。

#include <iostream>
using namespace std;

class MyClass{
	int x, y;
public:
	MyClass(int a, int b) {x = a, y = b;};
	friend int myadd(MyClass A);//フレンド関数の宣言
};

//フレンド関数の定義
int myadd(MyClass A)
{
	return A.x + A.y;//非公開メンバへのアクセス
}


int main(int, char**)
{
	MyClass A1(3, 4), A2(10, 5);

	cout << myadd(A1) <<"\n";//フレンド関数の呼び出し
	cout << myadd(A2) <<"\n";//

	return 0;
}

まず重要なことは”フレンド関数はメンバ関数ではない”という点です。フレンド関数の定義部分と呼び出し部分を見てください。 決して

//フレンド関数の定義
int MyClass::myadd(MyClass A)
{
	return A.x + A.y;//非公開メンバへのアクセス
}


//フレンド関数の呼び出し(main関数内)
	cout << A1.myadd(A1) <<"\n";
	cout << A2.myadd(A2) <<"\n";

とはなっていないでしょ?定義部分と呼び出し部分ともに通常の関数のように記述されているはずです。

次に注目するのは、フレンド関数には引数としてオブジェクトを渡し、そのオブジェクトを通じて非公開メンバへ アクセスしている所です。main関数で myadd(A1)とA1オブジェクトを引数として、A.xという形でMyClassクラスの 非公開メンバ変数xにアクセスしていますね。以上がフレンド関数を使う上でのルールです。

まぁ、これだけだと一体フレンド関数って何の役に立つのやらって感じですので、次にフレンド関数が 使用される典型的な例を示します。フレンド関数は複数のクラスで使用することが出来るのです。

#include <iostream>
using namespace std;

class MyClass2;//前方宣言

class MyClass1{
	int x, y;
public:
	MyClass1(int a, int b) {x = a, y = b;};
	friend int myadd(MyClass1 A, MyClass2 B);
};


class MyClass2{
	int x, y;
public:
	MyClass2(int a, int b) {x = a, y = b;};
	friend int myadd(MyClass1 A, MyClass2 B);
};



int myadd(MyClass1 A, MyClass2 B)
{
	return A.x + A.y + B.x + B.y;//複数のクラスの非公開メンバにアクセス
}


int main(int, char**)
{
	MyClass1 A(3, 4);
	MyClass2 B(10, 5);

	cout << myadd(A, B) <<"\n";

	return 0;
}

注意点としては、MyClass2を宣言する前にmyadd(MyClass1 A, MyClass2 B);なんて書かれては困るので、 前方宣言をしている点です。後は見てもらえれば分かりますように、MyClass1とMyClass2の2つのクラスの 非公開メンバ変数にアクセスしていますね。

フレンド関数はメンバ関数ではありませんので、thisポインタは持ちませんし、継承もされませんので 当然派生クラスのフレンド関数になったりはしません、留意しておいてください。

目次へ







?????

まだ未定です!

目次へ







?????

まだ未定です!

目次へ