目次
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; };
知らせ:
- 浮動小数点数、クラス オブジェクト、文字列は非型テンプレート パラメーターとして許可されません。
- 非型テンプレート パラメーターには、コンパイル時に確認された結果が必要です。
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 対の山括弧が続きます。
- 関数パラメータテーブル: テンプレート関数の基本パラメータ型と完全に同じである必要があります。異なる場合、コンパイラが奇妙なエラーを報告する可能性があります。
//模板特化 //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 解決策
- 実際には、宣言と定義をファイル「xxx.hpp」または xxx.h に入れることができます。これはお勧めです。
- テンプレート定義が明示的にインスタンス化される場合。この方法は非現実的であるため、お勧めできません。
- 定義を分離するときに、ドメイン内で型を使用する場合は、それが型であるか変数であるかを判断するために typename を追加する必要があります (そうしないと、コンパイラーはクラスの可能な静的メンバー変数を区別できません)。
4. テンプレートの概要
【アドバンテージ】
- このテンプレートはコードを再利用し、リソースを節約し、より迅速な反復開発を可能にし、C++ 標準テンプレート ライブラリ (STL) が作成されます。
- コードの柔軟性の向上
【欠陥】
- テンプレートによりコードが肥大化してコンパイル時間が長くなる可能性がある
- テンプレートのコンパイル エラーが発生すると、エラー メッセージが非常に煩雑になり、エラーを特定するのが困難になります。