かたわく

128381 ワード

「テンプレート」(Template)と関数テンプレートについて


関数テンプレートは、さまざまなデータ型の関数を生成する関数を作成するツールです.
#include <iostream>
using namespace std;

template <typename T>
T Add(T num1, T num2)
{
    return num1 + num2;
}

int main(void)
{
    cout << Add<int>(15, 20) << endl;
    cout << Add<double>(2.9, 3.7) << endl;

    return 0;
}
template <typename T>の代わりにtemplate <class T>を使用してもよい.
コンパイルフェーズでは、各タイプの資料に関数が生成されます.これは、実行速度ではなくコンパイル速度に影響します.
上記の定義を함수 템플릿(function template)と呼び、このテンプレートに基づいてコンパイラが生成する様々なタイプの関数を템플릿 함수(template function)と呼ぶ.

複数のタイプのテンプレートを宣言

#include <iostream>
using namespace std;

template <class T1, class T2>
void ShowData(double num)
{
    cout << (T1)num << ", " << (T2)num << endl;
}

int main(void)
{
    ShowData<char, int>(65);
    ShowData<char, int>(67);
    ShowData<char, double>(68.9);
    ShowData<short, double>(69.2);
    ShowData<short, double>(70.4);

    return 0;
}

特殊化関数テンプレート


場合によってはテンプレート関数の構成方法を例外とする必要があるが,この場合は함수 템플릿의 특수화(Specialization of Function Template)を用いる.
#include <iostream>
#include <cstring>
using namespace std;

template <typename T>
T Max(T a, T b)
{
    return a > b ? a : b;
}

// the length of string
template <>
char *Max(char *a, char *b)
{
    cout << "char* Max<char*>(char* a, char *b)" << endl;
    return strlen(a) > strlen(b) ? a : b;
}

// alphabetical order
template <>
const char *Max(const char *a, const char *b)
{
    cout << "const char* Max<const char *>(const char *a, const char* b)" << endl;
    return strcmp(a, b) > 0 ? a : b;
}

int main(void)
{
    cout << Max(11, 15) << endl;
    cout << Max('T', 'Q') << endl;
    cout << Max(3.5, 7.6) << endl;
    cout << Max("Simple", "Best") << endl; // const char *

    char str1[] = "Simple";
    char str2[] = "Best";
    cout << Max(str1, str2) << endl; // char *
    return 0;
}
上記の場合、char *型関数またはconst char *型関数はテンプレート関数ではなく、個別の関数を用いることがテンプレートの特殊化である.

クラステンプレート


関数をテンプレートとして定義するように、クラスをテンプレートとして定義することもできます.このように定義されたテンプレートを클래스 템플릿(class template)と呼び,これに基づいてコンパイラが生成したクラスを템플릿 클래스(template class)と呼ぶ.

クラステンプレートの定義

#include <iostream>
using namespace std;

template <typename T>
class Point
{
private:
    T xpos, ypos;

public:
    Point(T x = 0, T y = 0) : xpos(x), ypos(y)
    {
    }
    void ShowPosition() const
    {
        cout << '[' << xpos << ", " << ypos << ']' << endl;
    }
};

int main(void)
{
    Point<int> pos1(3, 4);
    pos1.ShowPosition();

    Point<double> pos2(2.4, 3.6);
    pos2.ShowPosition();

    Point<char> pos3('P', 'F'); // 좌표 정보를 문자로 표시
    pos3.ShowPosition();

    return 0;
}

クラステンプレートの宣言と定義を分離


クラステンプレートは、クラスの外部でメンバー関数を定義することもできます.
#include <iostream>
using namespace std;

template <typename T>
class Point
{
private:
    T xpos, ypos;

public:
    Point(T x = 0, T y = 0);
    void ShowPosition() const;
};

template <typename T>
Point<T>::Point(T x, T y) : xpos(x), ypos(y)
{
}

template <typename T>
void Point<T>::ShowPosition() const
{
    cout << '[' << xpos << ", " << ypos << ']' << endl;
}

int main(void)
{
    Point<int> pos1(3, 4);
    pos1.ShowPosition();

    Point<double> pos2(2.4, 3.6);
    pos2.ShowPosition();

    Point<char> pos3('P', 'F'); // 좌표 정보를 문자로 표시
    pos3.ShowPosition();

    return 0;
}

ファイルの分割

#ifndef POINT_TEMPLATE_H
#define POINT_TEMPLATE_H

template <typename T>
class Point
{
private:
    T xpos, ypos;

public:
    Point(T x = 0, T y = 0);
    void ShowPosition() const;
};

#endif
#include <iostream>
#include "PointTemplate.h"
using namespace std;

template <typename T>
Point<T>::Point(T x, T y) : xpos(x), ypos(y)
{
}

template <typename T>
void Point<T>::ShowPosition() const
{
    cout << '[' << xpos << ", " << ypos << ']' << endl;
}
#include <iostream>
#include "PointTemplate.h"
#include "PointTemplate.cpp"
using namespace std;

int main(void)
{
    Point<int> pos1(3, 4);
    pos1.ShowPosition();

    Point<double> pos2(2.4, 3.6);
    pos2.ShowPosition();

    Point<char> pos3('P', 'F');
    pos3.ShowPosition();
    return 0;
}
ヘッダファイルPointTemplate.hにはテンプレートPointの作成者とメンバー関数の定義が含まれる.
あるいはPointMaincppのPointTemplate.cppも含まれています.

アレイクラスのテンプレート化

#ifndef ARRAY_TEMPLATE_H
#define ARRAY_TEMPLATE_H

#include <iostream>
#include <cstdlib>
using namespace std;

template <typename T>
class BoundCheckArray
{
private:
	T *arr;
	int arrlen;

	BoundCheckArray(const BoundCheckArray &arr) {}
	BoundCheckArray &operator=(const BoundCheckArray &arr) {}

public:
	BoundCheckArray(int len);
	T &operator[](int idx);
	T operator[](int idx) const;
	int GetArrLen() const;
	~BoundCheckArray();
};

template <typename T>
BoundCheckArray<T>::BoundCheckArray(int len) : arrlen(len)
{
	arr = new T[len];
}

template <typename T>
T &BoundCheckArray<T>::operator[](int idx)
{
	if (idx < 0 || idx >= arrlen)
	{
		cout << "Array index out of bound exception" << endl;
		exit(1);
	}
	return arr[idx];
}

template <typename T>
T BoundCheckArray<T>::operator[](int idx) const
{
	if (idx < 0 || idx >= arrlen)
	{
		cout << "Array index out of bound exception" << endl;
		exit(1);
	}
	return arr[idx];
}

template <typename T>
int BoundCheckArray<T>::GetArrLen() const
{
	return arrlen;
}

template <typename T>
BoundCheckArray<T>::~BoundCheckArray()
{
	delete[] arr;
}

#endif
#ifndef POINT_H
#define POINT_H

#include <iostream>
using namespace std;

class Point
{
private:
    int xpos, ypos;

public:
    Point(int x = 0, int y = 0);
    friend ostream &operator<<(ostream &os, const Point &pos);
};

#endif
#include <iostream>
#include "Point.h"
using namespace std;

Point::Point(int x, int y) : xpos(x), ypos(y) {}

ostream &operator<<(ostream &os, const Point &pos)
{
    os << '[' << pos.xpos << ", " << pos.ypos << ']' << endl;
    return os;
}
#include <iostream>
#include "ArrayTemplate.h"
#include "Point.h"
using namespace std;

int main(void)
{
    // int형 정수 저장
    BoundCheckArray<int> iarr(5);
    for (int i = 0; i < 5; i++)
        iarr[i] = (i + 1) * 11;
    for (int i = 0; i < 5; i++)
        cout << iarr[i] << endl;

    // Point 객체 저장
    BoundCheckArray<Point> oarr(3);
    oarr[0] = Point(3, 4);
    oarr[1] = Point(5, 6);
    oarr[2] = Point(7, 8);
    for (int i = 0; i < oarr.GetArrLen(); i++)
        cout << oarr[i];

    // Point 객체의 주소 값 저장
    typedef Point *POINT_PTR;
    BoundCheckArray<POINT_PTR> parr(3);
    parr[0] = new Point(3, 4);
    parr[1] = new Point(5, 6);
    parr[2] = new Point(7, 8);
    for (int i = 0; i < parr.GetArrLen(); i++)
        cout << *(parr[i]);

    delete parr[0];
    delete parr[1];
    delete parr[2];
    return 0;
}

Point類テンプレートと平舗装類テンプレート


テンプレートクラス(Point<int>など)を格納できるオブジェクトを作成
#ifndef POINT_TEMPLATE
#define POINT_TEMPLATE

template <typename T>
class Point
{
private:
	T xpos, ypos;

public:
	Point(T x = 0, T y = 0);
	void ShowPosition() const;
};

template <typename T>
Point<T>::Point(T x, T y) : xpos(x), ypos(y)
{
}

template <typename T>
void Point<T>::ShowPosition() const
{
	cout << '[' << xpos << ", " << ypos << ']' << endl;
}

#endif
#ifndef ARRAY_TEMPLATE
#define ARRAY_TEMPLATE

#include <iostream>
#include <cstdlib>
using namespace std;

template <typename T>
class BoundCheckArray
{
private:
	T *arr;
	int arrlen;

	BoundCheckArray(const BoundCheckArray &arr) {}
	BoundCheckArray &operator=(const BoundCheckArray &arr) {}

public:
	BoundCheckArray(int len);
	T &operator[](int idx);
	T operator[](int idx) const;
	int GetArrLen() const;
	~BoundCheckArray();
};

template <typename T>
BoundCheckArray<T>::BoundCheckArray(int len) : arrlen(len)
{
	arr = new T[len];
}

template <typename T>
T &BoundCheckArray<T>::operator[](int idx)
{
	if (idx < 0 || idx >= arrlen)
	{
		cout << "Array index out of bound exception" << endl;
		exit(1);
	}
	return arr[idx];
}

template <typename T>
T BoundCheckArray<T>::operator[](int idx) const
{
	if (idx < 0 || idx >= arrlen)
	{
		cout << "Array index out of bound exception" << endl;
		exit(1);
	}
	return arr[idx];
}

template <typename T>
int BoundCheckArray<T>::GetArrLen() const
{
	return arrlen;
}

template <typename T>
BoundCheckArray<T>::~BoundCheckArray()
{
	delete[] arr;
}

#endif
#include <iostream>
#include "ArrayTemplate.h"
#include "PointTemplate.h"
using namespace std;

int main(void)
{
	BoundCheckArray<Point<int> > oarr1(3);
	oarr1[0] = Point<int>(3, 4);
	oarr1[1] = Point<int>(5, 6);
	oarr1[2] = Point<int>(7, 8);

	for (int i = 0; i < oarr1.GetArrLen(); i++)
		oarr1[i].ShowPosition();

	BoundCheckArray<Point<double> > oarr2(3);
	oarr2[0] = Point<double>(3.14, 4.31);
	oarr2[1] = Point<double>(5.09, 6.07);
	oarr2[2] = Point<double>(7.82, 8.54);

	for (int i = 0; i < oarr2.GetArrLen(); i++)
		oarr2[i].ShowPosition();

	typedef Point<int> *POINT_PTR;
	BoundCheckArray<POINT_PTR> oparr(3);
	oparr[0] = new Point<int>(11, 12);
	oparr[1] = new Point<int>(13, 14);
	oparr[2] = new Point<int>(15, 16);

	for (int i = 0; i < oparr.GetArrLen(); i++)
		oparr[i]->ShowPosition();

	delete oparr[0];
	delete oparr[1];
	delete oparr[2];

	return 0;
}

特定のテンプレートクラスのオブジェクトをパラメータとする一般的な関数の定義とfriend宣言


テンプレートではなく一般的な関数を定義してもよいし、クラステンプレート内でこれらの関数に対してfriend、例えばPoint<int>およびPoint<double>を宣言してもよい.
#include <iostream>
using namespace std;

template <typename T>
class Point
{
private:
    T xpos, ypos;

public:
    Point(T x = 0, T y = 0) : xpos(x), ypos(y)
    {
    }
    void ShowPosition() const
    {
        cout << '[' << xpos << ", " << ypos << ']' << endl;
    }
    friend Point<int> operator+(const Point<int> &, const Point<int> &);
    friend ostream &operator<<(ostream &os, const Point<int> &pos);
};

Point<int> operator+(const Point<int> &pos1, const Point<int> &pos2)
{
    return Point<int>(pos1.xpos + pos2.xpos, pos1.ypos + pos2.ypos);
}

ostream &operator<<(ostream &os, const Point<int> &pos)
{
    os << '[' << pos.xpos << ", " << pos.ypos << ']' << endl;
    return os;
}

int main(void)
{
    Point<int> pos1(2, 4);
    Point<int> pos2(4, 8);
    Point<int> pos3 = pos1 + pos2;
    cout << pos1 << pos2 << pos3;
    return 0;
}
上記のように、クラステンプレートはalban関数にfriendを宣言することもできます.

クラステンプレートの専門化


クラステンプレートの特殊化


クラステンプレートを特殊化するのは、特定のデータ型に基づいて生成されたオブジェクトに異なる動作スタイルを適用するためです.すなわち、クラステンプレートを専門化すると、テンプレートを構成するメンバー関数の一部またはすべての動作の違いを定義できます.
#include <iostream>
#include <cstring>
using namespace std;

template <typename T>
class Point
{
private:
    T xpos, ypos;

public:
    Point(T x = 0, T y = 0) : xpos(x), ypos(y)
    {
    }
    void ShowPosition() const
    {
        cout << '[' << xpos << ", " << ypos << ']' << endl;
    }
};

template <typename T>
class SimpleDataWrapper
{
private:
    T mdata;

public:
    SimpleDataWrapper(T data) : mdata(data)
    {
    }
    void ShowDataInfo(void)
    {
        cout << "Data: " << mdata << endl;
    }
};

template <>
class SimpleDataWrapper<char *>
{
private:
    char *mdata;

public:
    SimpleDataWrapper(char *data)
    {
        mdata = new char[strlen(data) + 1];
        strcpy(mdata, data);
    }
    void ShowDataInfo(void)
    {
        cout << "String: " << mdata << endl;
        cout << "Length: " << strlen(mdata) << endl;
    }
    ~SimpleDataWrapper() { delete[] mdata; }
};

template <>
class SimpleDataWrapper<Point<int> >
{
private:
    Point<int> mdata;

public:
    SimpleDataWrapper(int x, int y) : mdata(x, y)
    {
    }
    void ShowDataInfo(void)
    {
        mdata.ShowPosition();
    }
};

int main(void)
{
    SimpleDataWrapper<int> iwrap(170);
    iwrap.ShowDataInfo();
    SimpleDataWrapper<char *> swrap("Class Template Specialization");
    swrap.ShowDataInfo();
    SimpleDataWrapper<Point<int> > poswrap(3, 7);
    poswrap.ShowDataInfo();
    return 0;
}
char *型とPoint<int>型を特殊化した.

クラステンプレートの特定部分


T 1,T 2はいずれも専門化されておらず,そのうちの1つだけが部分的に特殊化できる.클래스 템플릿의 부분 특수화(class template partial specialization)と言います.
#include <iostream>
using namespace std;

template <typename T1, typename T2>
class MySimple
{
public:
    void WhoAreYou()
    {
        cout << "size of T1: " << sizeof(T1) << endl;
        cout << "size of T2: " << sizeof(T2) << endl;
        cout << "<typename T1, typename T2>" << endl;
    }
};

template <>
class MySimple<int, double>
{
public:
    void WhoAreYou()
    {
        cout << "size of int: " << sizeof(int) << endl;
        cout << "size of double: " << sizeof(double) << endl;
        cout << "<int, double>" << endl;
    }
};

template <typename T1>
class MySimple<T1, double> // T2를 double로 특수화
{
public:
    void WhoAreYou()
    {
        cout << "size of T1: " << sizeof(T1) << endl;
        cout << "size of double: " << sizeof(double) << endl;
        cout << "<T1, double>" << endl;
    }
};

int main(void)
{
    MySimple<char, double> obj1;
    obj1.WhoAreYou();
    MySimple<int, long> obj2;
    obj2.WhoAreYou();
    MySimple<int, double> obj3;
    obj3.WhoAreYou();
    return 0;
}
全体の専門化は一部の専門化より優先される.

テンプレートパラメータ


テンプレートを定義する場合、T、T 1、T 2などの未確定のデータ型を表す文字を템플릿 매개변수と呼びます.
そして、テンプレートパラメータに伝達される資料型情報を템플릿 인자と呼ぶ.

テンプレートパラメータには変数の宣言があります

#include <iostream>
using namespace std;

template <typename T, int len>
class SimpleArray
{
private:
    T arr[len];

public:
    T &operator[](int idx) { return arr[idx]; }
    SimpleArray<T, len> &operator=(const SimpleArray<T, len> &ref)
    {
        for (int i = 0; i < len; i++)
            arr[i] = ref.arr[i];
        return *this;
    }
};

int main(void)
{
    SimpleArray<int, 5> i5arr1;
    for (int i = 0; i < 5; i++)
        i5arr1[i] = i * 10;

    SimpleArray<int, 5> i5arr2;
    i5arr2 = i5arr1;
    for (int i = 0; i < 5; i++)
        cout << i5arr2[i] << ", ";
    cout << endl;

    SimpleArray<int, 7> i7arr1;
    for (int i = 0; i < 7; i++)
        i7arr1[i] = i * 10;

    SimpleArray<int, 7> i7arr2;
    i7arr2 = i7arr1;
    for (int i = 0; i < 7; i++)
        cout << i7arr2[i] << ", ";
    cout << endl;
    return 0;
}
値をテンプレートパラメータに渡すと、変数に渡す定数で異なるタイプのクラスを作成できます.
上記の場合、長さの異なる2つの配列オブジェクト間の代入および複製部分を考慮する必要はありません.

テンプレートパラメータは、デフォルト値を指定することもできます。

#include <iostream>
using namespace std;

template <typename T = int, int len = 7> // Default Value 지정
class SimpleArray
{
private:
    T arr[len];

public:
    T &operator[](int idx) { return arr[idx]; }
    SimpleArray<T, len> &operator=(const SimpleArray<T, len> &ref)
    {
        for (int i = 0; i < len; i++)
            arr[i] = ref.arr[i];
        return *this;
    }
};

int main(void)
{
    SimpleArray<> arr;
    for (int i = 0; i < 7; i++)
        arr[i] = i + 1;
    for (int i = 0; i < 7; i++)
        cout << arr[i] << " ";
    cout << endl;
    return 0;
}
テンプレートパラメータにデフォルト値を指定しても、テンプレートクラスのオブジェクトを作成するために<>シンボルを追加する必要があります.

テンプレートと静的


関数テンプレートと静的変数


関数テンプレート内でゾーン変数がstaticであることが宣言された場合、コンパイラはテンプレート関数に基づいてstaticゾーン変数を生成します.
#include <iostream>
using namespace std;

template <typename T>
void ShowStaticValue(void)
{
    static T num = 0;
    num += 1;
    cout << num << " ";
}

int main(void)
{
    ShowStaticValue<int>();
    ShowStaticValue<int>();
    ShowStaticValue<int>();
    cout << endl;
    ShowStaticValue<long>();
    ShowStaticValue<long>();
    ShowStaticValue<long>();
    cout << endl;
    ShowStaticValue<double>();
    ShowStaticValue<double>();
    ShowStaticValue<double>();
    cout << endl;
}
1 2 3 
1 2 3 
1 2 3

クラステンプレートと静的メンバー変数

staticメンバー変数は、変数として宣言されたクラス内のオブジェクトが共有できる変数です.
各テンプレート・クラスには、staticメンバー変数が保持されます.
#include <iostream>
using namespace std;

template <typename T>
class SimpleStaticMem
{
private:
    static T mem;

public:
    void AddMem(int num) { mem += num; }
    void ShowMem() { cout << mem << endl; }
};

template <typename T>
T SimpleStaticMem<T>::mem = 0; // static 멤버 초기화

int main(void)
{
    SimpleStaticMem<int> obj1;
    SimpleStaticMem<int> obj2;
    obj1.AddMem(2);
    obj2.AddMem(3);
    obj1.ShowMem();
    SimpleStaticMem<long> obj3;
    SimpleStaticMem<long> obj4;
    obj3.AddMem(100);
    obj4.ShowMem();

    return 0;
}
5
100
テンプレート関連定義では、コンパイラがテンプレートの一部または全部を定義していることを通知するために、template<typename T>またはtemplate<>を宣言する必要があります.
定義部分がTであれば、Tについて説明するために<typename T>の形で追加すればよく、Tが存在しなければ<>の形で簡単に宣言すればよい.