C++ 初級 - テンプレート上級

 

目次

1. 非型テンプレートパラメータとコンテナ配列

2. テンプレートの特化

2.1 コンセプト

2.2 関数テンプレートの特殊化

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

2.3.1 全特化

2.3.2 部分的な専門化

2.3.3 クラステンプレート特化の応用例

3. テンプレート別編集

3.1 分冊とは

3.2 テンプレートの個別コンパイル

 3.3 解決策

4. テンプレートの概要


1. 非型テンプレートパラメータとコンテナ配列

テンプレート パラメーターの分類: 型は非型パラメーターに関与します。

type パラメータは、テンプレート パラメータ リストに表示され、その後に class や typename などのパラメータ タイプ名が続きます。

非型パラメータは、クラス(関数)テンプレートのパラメータとして定数を使用することであり、そのパラメータはクラス(関数)テンプレート内で定数として使用できます。

//#define N 100
//非类型模板参数,不是类型,是常量
//模板参数可以给缺省值,和函数参数相似
//模板参数只能用于整形,浮点及自定义类型都不可以
// 定义一个模板类型的静态数组
template<class T, size_t N = 10>
class array
{
public:
	T& operator[](size_t index) { return _array[index]; }
	const T& operator[](size_t index)const { return _array[index]; }

	size_t size()const { return _size; }
	bool empty()const { return 0 == _size; }

private:
	T _array[N];
	size_t _size;
};

知らせ:

  1.         浮動小数点数、クラス オブジェクト、文字列は非型テンプレート パラメーターとして許可されません。
  2.         非型テンプレート パラメーターには、コンパイル時に確認された結果が必要です。

C++11 では、配列コンテナーが新たに追加されました。つまり、非型テンプレート パラメーターの使用、定数の受け渡し、配列の使用、そして最下層は配列のカプセル化です。

int main1() {
	//非类型模板参数只能传递常量
	std::array<int,10> a1;// 100
	std::array<double, 1000> a2;// 1000
	int a3[10];

	std::cout << sizeof(a1) << std::endl;
	std::cout << sizeof(a2) << std::endl;

	//普通数组越界不一定被查到
	//arrary只要越界就会检查到
	
	//指针解引用--检查是否越界,只针对越界写,越界读不检查
	//a3[15] = 100;

	//主要是函数调用operator[],只要越界,就会检查出来
	a1[15] = 0;
	return 0;
}

範囲外の通常の配列はチェックされませんが、範囲外の配列は直接チェックされます。

2. テンプレートの特化

2.1 コンセプト

通常、テンプレートを使用して型に依存しないコードを実装できますが、一部の特殊な型では、誤った結果が得られる場合があり、特別な処理が必要になります。たとえば、大小比較に特別に使用される関数テンプレートを実装する、または Functionテンプレートを使用してポインターを渡すテンプレート

struct Date {
	Date(int year, int month, int day)
		:_year(year)
		, _month(month)
		, _day(day)
	{}
	bool operator>(const Date& d) const;
	bool operator<(const Date& d) const {
		if ((_year < d._year) || (_year == d._year && _month < d._month) || \
			(_year == d._year && _month == d._month && _day < d._day)) {
			return true;
		}
		return false;
	}
	int _year;
	int _month;
	int _day;
};

//模板特化
//1、函数模板特化 -- 参数匹配

template<class T>
bool Greater(const T left, const T right) {
	return left > right;
}

int main() {
	std::cout << Greater(1, 2) << std::endl;

	Date d1(2023, 7, 7);
	Date d2(2022, 7, 8);
	std::cout << Greater(d1, d2) << std::endl;

	//需要特化,针对某些类型进行特殊化处理
	Date* p1 = &d1;
	Date* p2 = &d2;
	std::cout << Greater(p1, p2) << std::endl;

	Thb::greater<Date> lessFunc1;
	std::cout << lessFunc1(d1, d2) << std::endl;

	Thb::greater<Date*> lessFunc2;
	std::cout << lessFunc2(p1, p2) << std::endl;
	return 0;
}

ほとんどの場合、Greater は正常に比較できますが、特殊なシナリオでは誤った結果が得られることがわかります。上記の例では、p1 が指す d1 は p2 が指す d2 オブジェクトよりも明らかに大きいですが、Greaterr は内部で p1 と p2 が指すオブジェクトの内容を比較するのではなく、p1 と p2 のアドレスを比較します。これは期待に応えられず、間違っています。

この時点で、テンプレートを特殊化する必要があります。つまり、元のテンプレート クラスに基づいて、特殊な型に特化した実装メソッドです。テンプレートの特化は、関数テンプレートの特化とクラス テンプレートの特化に分かれます。

2.2 関数テンプレートの特殊化

関数テンプレートの特殊化手順は次のとおりです。

  1.         最初に基本的な関数テンプレートが必要です
  2.         キーワード テンプレートの後には、空の山かっこ <> が続きます。
  3.         関数名の後には、特殊化する型を指定する 1 対の山括弧が続きます。
  4.         関数パラメータテーブル: テンプレート関数の基本パラメータ型と完全に同じである必要があります。異なる場合、コンパイラが奇妙なエラーを報告する可能性があります。
//模板特化
//1、函数模板特化 -- 参数匹配

template<class T>
bool Greater(const T left, const T right) {
	return left > right;
}

template<>
bool Greater<Date*>(Date* left, Date* right) {
	return *left > *right;
}


int main() {
	std::cout << Greater(1, 2) << std::endl;

	Date d1(2023, 7, 7);
	Date d2(2022, 7, 8);
	std::cout << Greater(d1, d2) << std::endl;

	//需要特化,针对某些类型进行特殊化处理
	Date* p1 = &d1;
	Date* p2 = &d2;
	std::cout << Greater(p1, p2) << std::endl;

	Thb::greater<Date> lessFunc1;
	std::cout << lessFunc1(d1, d2) << std::endl;

	Thb::greater<Date*> lessFunc2;
	std::cout << lessFunc2(p1, p2) << std::endl;
	return 0;
}

テンプレート パラメーターが最初に照合され、テンプレート生成の代わりに特殊なバージョンが呼び出されます。型の優先順位を一致させるための関数のオーバーロードの原理と似ています。

注: 一般に、関数テンプレートで処理できない型、または正しく処理されない型が見つかった場合、実装を簡素化するために関数は通常直接実装されます。

bool Less(Date* left, Date* right)
{
     return *left < *right;
}

この種の実装はシンプルかつ明確で、コードは非常に読みやすく、記述も簡単です。複雑なパラメーター型を持つ一部の関数テンプレートでは、特殊化中に特殊化が行われるため、関数テンプレートの特殊化は推奨されません。

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

2.3.1 全特化

完全な特殊化は、テンプレート パラメーター リスト内のすべてのパラメーターを決定することです。

template<class T1, class T2>
class Data
{
public:
	Data() { std::cout << "Data<T1, T2>" << std::endl; }
private:
	T1 _d1;
	T2 _d2;
};

template<>
class Data<int, char>
{
public:
	Data() { std::cout << "Data<int, char>" << std::endl; }
private:
	int _d1;
	char _d2;
};
void TestVector()
{
	Data<int, int> d1;
	Data<int, char> d2;
}

呼び出しの原則は、最も一致するテンプレート パラメーターを最初に呼び出すことです。

2.3.2 部分的な専門化

部分的特殊化: テンプレート パラメーターの設計をさらに条件付きで制限する特殊化。たとえば、次のテンプレート クラスの場合:

template<class T1, class T2>
class Data
{
public:
	Data() { std::cout << "Data<T1, T2>" << std::endl; }
private:
	T1 _d1;
	T2 _d2;
};

部分特化には 2 つのタイプがあります。

        1)部分特化

        テンプレート パラメーター クラス リスト内のパラメーターの一部を特殊化します。

// 将第二个参数特化为int
template <class T1>
class Data<T1, int>
{
public:
	Data() { cout << "Data<T1, int>" << endl; }
private:
	T1 _d1;
	int _d2;
};

        2)パラメータのさらなる制限

        部分的特殊化は、一部のパラメーターの特殊化だけを指すのではなく、テンプレート パラメーターの条件をさらに制限するために設計された特殊化されたバージョンを指します。

//两个参数偏特化为指针类型
template <typename T1, typename T2>
class Data <T1*, T2*>
{
public:
	Data() { cout << "Data<T1*, T2*>" << endl; }

private:
	T1 _d1;
	T2 _d2;
};
//两个参数偏特化为引用类型
template <typename T1, typename T2>
class Data <T1&, T2&>
{
public:
	Data(const T1& d1, const T2& d2)
		: _d1(d1)
		, _d2(d2)
	{
		cout << "Data<T1&, T2&>" << endl;
	}

private:
	const T1& _d1;
	const T2& _d2;
};


void test2()
{
	Data<double, int> d1; // 调用特化的int版本
	Data<int, double> d2; // 调用基础的模板 
	Data<int*, int*> d3; // 调用特化的指针版本
	Data<int&, int&> d4(1, 2); // 调用特化的指针版本
}

上記からわかるように、テンプレート パラメーターはポインターまたは参照として特殊化できます。

2.3.3 クラステンプレート特化の応用例

小なり比較には次のクラス テンプレートがあります。

#include<vector>
#include <algorithm>
template<class T>
struct Less
{
	bool operator()(const T& x, const T& y) const
	{
		return x < y;
	}
};
int main()
{
	Date d1(2022, 7, 7);
	Date d2(2022, 7, 6);
	Date d3(2022, 7, 8);
	vector<Date> v1;
	v1.push_back(d1);
	v1.push_back(d2);
	v1.push_back(d3);
	// 可以直接排序,结果是日期升序
	sort(v1.begin(), v1.end(), Less<Date>());
	vector<Date*> v2;
	v2.push_back(&d1);
	v2.push_back(&d2);
	v2.push_back(&d3);

	// 可以直接排序,结果错误日期还不是升序,而v2中放的地址是升序
	// 此处需要在排序过程中,让sort比较v2中存放地址指向的日期对象
	// 但是走Less模板,sort在排序时实际比较的是v2中指针的地址,因此无法达到预期
	sort(v2.begin(), v2.end(), Less<Date*>());
	return 0;
}

上記のプログラムの結果を観察すると、日付オブジェクトを直接ソートでき、結果が正しいことがわかります。ただし、ソート対象の要素がポインタの場合、結果が正しくない可能性があります。理由: sort は最終的に Less テンプレートのメソッドに従って比較されるため、ポインターが指す空間の内容ではなく、ポインターのみを比較します。このとき、クラス バージョンの特殊化を使用して上記に対処できます。問題:

// 对Less类模板按照指针方式特化
template<>
struct Less<Date*>
{
 bool operator()(Date* x, Date* y) const
 {
 return *x < *y;
 }
};

特定のテンプレートの特殊化が必要な場合は、ライブラリに実装されたファンクター クラスを使用してクラス テンプレートの特殊化を実装できます。

//针对指针的优先级队列需要进行特化处理
namespace std{
	template<>
	class less<Date*> {
	public:
		bool operator()(const Date* left, const Date* right) const {
			return *left < *right;
		}
	};
}

int main() {

	//此时结果是按地址比较的,需要特殊化处理,使其按照数据比较
	std::priority_queue<Date, std::vector<Date>, std::less<Date>> dq1;
	std::priority_queue<Date*, std::vector<Date*>, std::less<Date*>> dq2;
	dq2.push(new Date(2022, 9, 29));
	dq2.push(new Date(2022, 9, 27));
	dq2.push(new Date(2022, 9, 25));
	dq2.push(new Date(2022, 9, 30));
	dq2.push(new Date(2022, 10, 31));
	std::cout << (dq2.top())->_day << std::endl;

	return 0;
}

3. テンプレート別編集

3.1 分冊とは

プログラム (プロジェクト) は複数のソース ファイルによって共同実装され、各ソース ファイルは個別にコンパイルされてオブジェクト ファイルが生成され、最後にすべてのオブジェクト ファイルをリンクして 1 つの実行可能ファイルを形成するプロセスを個別コンパイル モードと呼びます。

3.2 テンプレートの個別コンパイル

以下のようなシナリオの場合、テンプレートの宣言と定義を分離し、ヘッダファイルで宣言を行い、ソースファイルで定義を完了します。

// a.h
template<class T>
T Add(const T& left, const T& right);


//a.cpp
template<class T>
T Add(const T& left, const T& right) {
	return left + right;
}


//main.cpp
#include"a.h"

int main() {
	std::cout << Add(1, 2) << std::endl;
	return 0;
}

分析します:

 3.3 解決策

  1. 実際には、宣言と定義をファイル「xxx.hpp」または xxx.h に入れることができます。これはお勧めです。
  2. テンプレート定義が明示的にインスタンス化される場合。この方法は非現実的であるため、お勧めできません。
  3. 定義を分離するときに、ドメイン内で型を使用する場合は、それが型であるか変数であるかを判断するために typename を追加する必要があります (そうしないと、コンパイラーはクラスの可能な静的メンバー変数を区別できません)。

 

4. テンプレートの概要

【アドバンテージ】

  1.         このテンプレートはコードを再利用し、リソースを節約し、より迅速な反復開発を可能にし、C++ 標準テンプレート ライブラリ (STL) が作成されます。
  2.         コードの柔軟性の向上

【欠陥】

  1.         テンプレートによりコードが肥大化してコンパイル時間が長くなる可能性がある
  2.         テンプレートのコンパイル エラーが発生すると、エラー メッセージが非常に煩雑になり、エラーを特定するのが困難になります。

おすすめ

転載: blog.csdn.net/IfYouHave/article/details/131318231