C++テンプレートテンプレート使用法まとめ
<スパン はじめに
テンプレートとは、プログラミング設計言語C++において、型を引数に用いて汎用的なプログラミングを支援することを指し、C++の標準ライブラリには、STLやIO Streamなど、ほとんどがテンプレートの考え方を取り入れた便利な関数が多数用意されています。
<スパン 関数テンプレート
c++入門では、以下のようなswap(int&, int&)のような関数に出会う人が多いでしょう。
void swap(int&a , int& b) {
int temp = a;
a = b;
b = temp;
}
しかし、long、string、カスタムクラスのスワップ関数に対応する場合、上記のコードと似ていますが、種類が異なるので、スワップ関数のテンプレートを定義して、異なる種類のスワップ関数のコードを再利用するようにします。
template <class identifier> function_declaration;
template <typename identifier> function_declaration;
swap関数テンプレートを宣言し、定義するコードは以下のとおりです。
//method.h
template<typename T> void swap(T& t1, T& t2);
#include "method.cpp"
//method.cpp
template<typename T> void swap(T& t1, T& t2) {
T tmpT;
tmpT = t1;
t1 = t2;
t2 = tmpT;
}
以上がテンプレートの宣言と定義ですが、テンプレートのインスタンス化はどのように行われるのでしょうか?テンプレートのインスタンス化は、プログラマーではなく、コンパイラが行うものです。
//main.cpp
#include <stdio.h>
#include "method.h"
int main() {
// template method
int num1 = 1, num2 = 2;
swap<int>(num1, num2);
printf("num1:%d, num2:%d\n", num1, num2);
return 0;
}
ここではswap関数を使用していますが、swapの定義が含まれていないとコンパイルがエラーになります。そのため、method.hファイルの最終行に#include "method.cpp"を追加する必要があります。
クラステンプレート
int型、long型、string型などをサポートできるスタックの簡単なクラスを書くとします。クラステンプレートを使用しない場合、3つ以上のスタッククラスを書かなければならず、コードは基本的に同じになります。クラステンプレートを使えば、単純なスタックテンプレートを定義し、それを必要に応じて int スタック、long スタック、string スタックとしてインスタンス化することができます。
//statck.h
template <class T> class Stack {
public:
Stack();
~Stack();
void push(T t);
T pop();
bool isEmpty();
private:
T *m_pT;
int m_maxSize;
int m_size;
};
#include "stack.cpp"
//stack.cpp
template <class T> Stack<T>::Stack(){
m_maxSize = 100;
m_size = 0;
m_pT = new T[m_maxSize];
}
template <class T> Stack<T>::~Stack() {
delete [] m_pT ;
}
template <class T> void Stack<T>::push(T t) {
m_size++;
m_pT[m_size - 1] = t;
}
template <class T> T Stack<T>::pop() {
T t = m_pT[m_size - 1];
m_size --;
return t;
}
template <class T> bool Stack<T>::isEmpty() {
return m_size == 0;
}
上記では、クラステンプレートであるスタックを定義していますが、これは単純で、クラステンプレートの使い方を説明するためだけのもので、スタック上の要素は最大100個までしかサポートできません。
//main.cpp
#include <stdio.h>
#include "stack.h"
int main() {
Stack<int> intStack;
intStack.push(1);
intStack.push(2);
intStack.push(3);
while (!intStack.isEmpty()) {
printf("num:%d\n", intStack.pop());
}
return 0;
}
<スパン
テンプレート・パラメータ
テンプレートは,通常の型パラメータintの他に,以下のようなデフォルトのテンプレートパラメータを持つことができます.
template<class T, T def_val> class Stack{...}
上記のクラステンプレートのスタックは、最大100個の要素しかサポートできないという制限があります。このスタックの最大要素数は、テンプレートパラメータを使用して設定することができますが、そうでない場合は、デフォルトの最大値を100に設定します。
//statck.h
template <class T,int maxsize = 100> class Stack {
public:
Stack();
~Stack();
void push(T t);
T pop();
bool isEmpty();
private:
T *m_pT;
int m_maxSize;
int m_size;
};
#include "stack.cpp"
//stack.cpp
template <class T,int maxsize> Stack<T, maxsize>::Stack(){
m_maxSize = maxsize;
m_size = 0;
m_pT = new T[m_maxSize];
}
template <class T,int maxsize> Stack<T, maxsize>::~Stack() {
delete [] m_pT ;
}
template <class T,int maxsize> void Stack<T, maxsize>::push(T t) {
m_size++;
m_pT[m_size - 1] = t;
}
template <class T,int maxsize> T Stack<T, maxsize>::pop() {
T t = m_pT[m_size - 1];
m_size --;
return t;
}
template <class T,int maxsize> bool Stack<T, maxsize>::isEmpty() {
return m_size == 0;
}
使用例は次のとおりです。
//main.cpp
#include <stdio.h>
#include "stack.h"
int main() {
int maxsize = 1024;
Stack<int,1024> intStack;
for (int i = 0; i < maxsize; i++) {
intStack.push(i);
}
while (!intStack.isEmpty()) {
printf("num:%d\n", intStack.pop());
}
return 0;
}
<スパン テンプレート特化型
あるテンプレートに対して別の実装を定義したい場合、テンプレートの特殊化を利用することができます。例えば、スタッククラスのテンプレートを定義すると、char*型のスタックであれば、charポインタを保持しているだけなので、charポインタが指すメモリは無効かもしれないし、スタックはスタック要素のcharポインタをポップアップして、既に無効かもしれないメモリを指すので、charのデータを全てスタッククラスにコピーできるようにしたいです。また、我々が定義したスワップ関数のテンプレートは、ベクトルまたはリストと他のコンテナ型は、コンテナが大きなオブジェクトを保存する場合、それは多くのメモリとパフォーマンスの低下を取るため、一時的に大きなオブジェクトを保存する生成するために、これらは解決するためのテンプレートの特殊化が必要です。
関数テンプレートの特化
例えば、2つのベクトルに多数の要素<int>がある場合、本来のswap関数を使うと、tmpT = t1がt1の要素を全てコピーしなければならず、メモリを大量に消費してパフォーマンスが低下するので、我々のシステムでは、この問題をvector.swap関数で解決します、そのコードは以下の通りです。
//method.h
template<class T> void swap(T& t1, T& t2);
#include "method.cpp"
#include <vector>
using namespace std;
template<class T> void swap(T& t1, T& t2) {
T tmpT;
tmpT = t1;
t1 = t2;
t2 = tmpT;
}
template<> void swap(std::vector<int>& t1, std::vector<int>& t2) {
t1.swap(t2);
}
template<>という接頭辞は、これがテンプレート・パラメータなしで記述される特殊化であることを示します。
//main.cpp
#include <stdio.h>
#include <vector>
#include <string>
#include "method.h"
int main() {
using namespace std;
// template method
string str1 = "1", str2 = "2";
swap(str1, str2);
printf("str1:%s, str2:%s\n", str1.c_str(), str2.c_str());
vector<int> v1, v2;
v1.push_back(1);
v2.push_back(2);
swap(v1, v2);
for (int i = 0; i < v1.size(); i++) {
printf("v1[%d]:%d\n", i, v1[i]);
}
for (int i = 0; i < v2.size(); i++) {
printf("v2[%d]:%d\n", i, v2[i]);
}
return 0;
}
vector<int> のスワップコードはまだかなり限定的なので、テンプレート特化ですべてのベクタースワップを解決したい場合、どうすればいいかというと、次のコードを入れるだけです。
template<> void swap(std::vector<int>& t1, std::vector<int>& t2) {
t1.swap(t2);
}
Change to
template<class V> void swap(std::vector<V>& t1, std::vector<V>& t2) {
t1.swap(t2);
}
//compare.h
template <class T>
class compare
{
public:
bool equal(T t1, T t2)
{
return t1 == t2;
}
};
#include <iostream>
#include "compare.h"
int main()
{
using namespace std;
char str1[] = "Hello";
char str2[] = "Hello";
compare<int> c1;
compare<char *> c2;
cout << c1.equal(1, 1) << endl; //compare two arguments of type int
cout << c2.equal(str1, str2) << endl; //compare two arguments of type char *
return 0;
}
これだけです。残りのコードは変わりません。
クラステンプレートの特殊化
以下の比較コードをご覧ください。
//compare.h
#include <string.h>
template <class T>
class compare
{
public:
bool equal(T t1, T t2)
{
return t1 == t2;
}
};
template<>class compare<char *>
{
public:
bool equal(char* t1, char* t2)
{
return strcmp(t1, t2) == 0;
}
};
2つの整数を比較する場合、compareのequalメソッドは正しいのですが、compareのテンプレートパラメータがchar*の場合、このテンプレートは動作しないので、以下のように修正します。
//main.cpp
#include <stdio.h>
#include "stack.h"
#include "shape.h"
int main() {
Stack<Circle*> pcircleStack;
Stack<Shape*> pshapeStack;
pcircleStack.push(new Circle);
pshapeStack = pcircleStack;
return 0;
}
main.cpp ファイルは変更されず、このコードは問題なく動作します。
テンプレート型変換
カスタムスタックテンプレートを思い出しながら、プログラムでShapeクラスとCircleクラスを以下のコードで定義したとします。
//statck.h
template <class T> class Stack {
public:
Stack();
~Stack();
void push(T t);
T pop();
bool isEmpty();
template<class T2> operator Stack<T2>();
private:
T *m_pT;
int m_maxSize;
int m_size;
};
#include "stack.cpp"
そして、このような使い方ができるようにしたいのです。
template <class T> Stack<T>::Stack(){
m_maxSize = 100;
m_size = 0;
m_pT = new T[m_maxSize];
}
template <class T> Stack<T>::~Stack() {
delete [] m_pT ;
}
template <class T> void Stack<T>::push(T t) {
m_size++;
m_pT[m_size - 1] = t;
}
template <class T> T Stack<T>::pop() {
T t = m_pT[m_size - 1];
m_size --;
return t;
}
template <class T> bool Stack<T>::isEmpty() {
return m_size == 0;
}
template <class T> template <class T2> Stack<T>::operator Stack<T2>() {
Stack<T2> StackT2;
for (int i = 0; i < m_size; i++) {
StackT2.push((T2)m_pT[m_size - 1]);
}
return StackT2;
}
Stack<Shape*> は Stack<Circle*> の親ではないので、ここではコンパイルされません。しかし、そのように動作させたいので、変換演算子を定義しなければならず、Stackコードは次のようになります。
//main.cpp
#include <stdio.h>
#include "stack.h"
#include "shape.h"
int main() {
Stack<Circle*> pcircleStack;
Stack<Shape*> pshapeStack;
pcircleStack.push(new Circle);
pshapeStack = pcircleStack;
return 0;
}
class Util {
public:
template <class T> bool equal(T t1, T t2) {
return t1 == t2;
}
};
int main() {
Util util;
int a = 1, b = 2;
util.equal<int>(1, 2);
return 0;
}
class Util {
public:
template <class T> static bool equal(T t1, T t2) {
return t1 == t2;
}
};
int main() {
int a = 1, b = 2;
Util::equal<int>(1, 2);
return 0;
}
こうすることで、Stack<Circle>やStack<Circle*>をStack<Shape>やStack<Shape*>に自動的に変換できるのですが、変換の型が Stack<int> to Stack< Shape> だとコンパイラからエラーが報告されることに注意してください。
<スパン その他
以下のコードで、クラスがテンプレート引数を持たず、テンプレート引数を持つメンバー関数を持つことが可能です。
class Util {
public:
template <class T> bool equal(T t1, T t2) {
return t1 == t2;
}
};
int main() {
Util util;
int a = 1, b = 2;
util.equal<int>(1, 2);
return 0;
}
次のコードで、Utilのイコールを静的と宣言することもできます。
関連
-
undefinederror: 'dynamic_cast' の前に unqualified-id を指定する必要があります。
-
C++ 文字列における c_str(), data(), copy(p,n) 関数の使用法
-
VCのグローバル変数が*.objで既に定義されている場合の問題点
-
void* から char* への無効な変換」および「文字列定数から 'char*' への非推奨の変換」を解決 "
-
エラー: コンストラクタ、デストラクタ、または '.' トークンの前に型変換が必要です。
-
非静的メンバ関数の無効な使用
-
"エラー:不完全なクラス型へのポインタは許可されません。"の前方宣言。
-
c++ 11 random ライブラリの簡単な使い方
-
ベクトル添え字が範囲外のコンテナの使用、その他類似のエラー
-
デバッグエラー Assertion Failed 問題について
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
error: '.' トークンの前に未修飾の ID が必要です。
-
C/C++共通エラーの概要
-
デバッグアサーションに失敗した」場合の解決策の一つ(着想)
-
C++ [エラー] 'std::string {aka std::basic_string<char>}' を 'char*' に変換できないエラー
-
const char*' から 'char*' への変換が無効です。
-
const char*' から `char*' への変換が無効な場合の対処法
-
警告を表示します。ISO C++は文字列定数を'char*'に変換することを禁じています[-Write-strings]。
-
抽象クラス型 "my class "のオブジェクトは使用できません 解決方法
-
ランタイムエラー: 'std::logic_error' のインスタンスを投げた後に terminate が呼び出されました。
-
c/c++の "undefined reference to "の解決法