C++ テンプレートの詳細な探索: 基本から高度なアプリケーションまで

目次

1. 汎用プログラミング

1.1 なぜ汎用プログラミングが必要なのでしょうか?

2. テンプレート

2.1 コンセプト

2.2 関数テンプレート

2.2.1 コンセプト

2.2.2 文法

2.2.3 例

 2.2.4 テンプレートのインスタンス化

暗黙的なインスタンス化

表示のインスタンス化

2.2.5 テンプレートパラメータのマッチング原則

2.3 クラステンプレート

2.3.1 コンセプト

2.3.2 構文

2.3.3 例

2.3.4 注意事項

2.3.5 解釈上の注意

1. メンバー関数の定義:

2. テンプレートパラメータの導出:

3. テンプレートの特化:

3. テンプレートファイルの書き込み(特殊)

1. 汎用プログラミング

汎用プログラミングについて話すときは、通常、さまざまなデータ型を効率的に処理するためにプログラミング言語で汎用コードを記述する方法を指します。これにより、コードがより柔軟で汎用的になり、再利用可能になります。C++ では、汎用プログラミングは主にテンプレートを通じて実装され、複数のデータ型に適用できる汎用コードを作成できます。

1.1 なぜ汎用プログラミングが必要なのでしょうか?

コードを記述するとき、多くの場合、さまざまな種類のデータを処理する必要があります。各データ型に特化したコードを毎回記述すると、コードが冗長になり、効率が低下します。ジェネリック プログラミングの目標は、書き直すことなくさまざまなデータ型に適用できるジェネリック コードを作成することで、この問題を解決することです。

例えば:

データをやり取りするための一般的な関数を実装したい C言語では名前の異なる関数しか実装できず非常に面倒 C++では関数のオーバーロードも実装できますが、オーバーロードされた関数はタイプが異なると、コードの再利用率が比較的低く、新しいタイプが出現する限り、対応する関数の実装を手動で追加する必要があり、コードの保守性も低下します。

void Swap(int &left, int &right) {
    int temp = left;
    left = right;
    right = temp;
}

void Swap(double &left, double &right) {
    double temp = left;
    left = right;
    right = temp;
}

void Swap(char &left, char &right) {
    char temp = left;
    left = right;
    right = temp;
}

//其他类型
.........

それで、他の方法はありますか?必要な型を指定するだけで、コンパイラが対応するバージョンの関数を自動的に実装します。

C++ テンプレートは、この問題を解決する強力なツールです。

2. テンプレート

2.1 コンセプト

C++ テンプレートは、汎用プログラミングを実装するための重要な機能であり、複数のデータ型に適用できる汎用コードを作成できます。テンプレートは、共通のコンテナー、アルゴリズム、およびデータ構造を作成するために、C++、特に標準ライブラリーで広く使用されています。

C++ には、関数テンプレートクラス テンプレートという2 つの主なタイプのテンプレートがあります

2.2 関数テンプレート

2.2.1 コンセプト

関数テンプレートは、ジェネリック関数を作成するための C++ のメカニズムです。これにより、データ型ごとに個別の関数を作成することなく、複数のデータ型で使用できるジェネリック関数を作成できます。関数テンプレートは、C++ で汎用プログラミングを実現するための重要なツールの 1 つです。

2.2.2 文法

関数テンプレートの構文は単純で、テンプレート ヘッダーと関数本体で構成されます

 その中で、型パラメータのプレースホルダーtemplate <typename T>であるテンプレートが宣言されています関数のパラメーターと戻り値の型で使用されると、コンパイラーはパラメーターの型に従って実際のデータ型を自動的に決定します。(typename はテンプレート パラメーターのキーワードを定義するために使用され、class も使用できます ( class の代わりに struct を使用できないことに注意してください))。TT

2.2.3 例

異なるデータ型で使用できる汎用関数 (maximum 関数など) を作成できます。

 2.2.4 テンプレートのインスタンス化

関数テンプレートのインスタンス化とは、関数テンプレートの使用時に渡される実際のパラメーターの型に従って、コンパイラーが特定の型の関数実装コードを生成するプロセスを指します。このプロセスは、特定のタイプのパラメーターに対して正しいコードが生成されることを保証するために、コンパイル段階で実行されます。

テンプレートパラメータのインスタンス化は、暗黙的なインスタンス化と明示的なインスタンス化に分けられます。

暗黙的なインスタンス化

これは、関数テンプレートがコード内で呼び出されるとき、コンパイラーは、渡されたパラメーターの型に従って、特定のデータ型の関数実装を暗黙的に生成することを意味します。これは、関数テンプレートをインスタンス化する一般的な方法です。

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

int main() {
    int result = Max(5, 10); // 隐式实例化为 int Max(int a, int b)
    return 0;
}

を呼び出すと仮定するとMax(5, 10)、コンパイラは次の手順を実行して関数テンプレートをインスタンス化します。

  1. コンパイラは呼び出しを認識しMax(5, 10)、関数テンプレートをインスタンス化する必要がありますMax
  2. コンパイラはパラメータ5と を分析して10、それらの型が何であるかを判断しますint
  3. コンパイラはTテンプレート パラメータを に置き換えint、次のような特定の実装を生成します。

 では、このように呼び出すにはどうすればよいでしょうか?

int a = 2;
double b = 3.0;

Max(a, b);

問題の性質

このステートメントはコンパイルできません。コンパイラは、異なる型の複数の実パラメータを検出した場合、適切なテンプレート パラメータの型を決定する必要があります。ただし、場合によっては、テンプレート パラメーター リストにはテンプレート パラメーターが 1 つしかないにもかかわらず、実際の引数は異なる型である可能性があるため、テンプレート パラメーターの型を明確に決定できない場合があります。

型推論と型変換

テンプレートのインスタンス化中、コンパイラーは通常、暗黙的な型変換を実行しません。これは、結果があいまいになる可能性があるためです。たとえば、あなたの場合、コンパイラはTテンプレート パラメータを または として推論するint必要があるかどうかを知らないdoubleため、正しいインスタンス化を行うことができません。

注: テンプレートでは、コンパイラーは通常、型変換操作を実行しません。

これを処理するには、次の 2 つの一般的な方法があります。

1. ユーザーによる強制変換

2. 明示的なインスタンス化

強制変換

Max(a, (int)b);

//或者

Max((double)a, b);
表示のインスタンス化

関数名の後の <> でテンプレート パラメーターの実際の型を指定すると、コンパイラーにインスタンス化の型を明示的に伝え、テンプレート パラメーターの推論の問題を回避できます。

Max<int>(a, b);

//或者

Max<double>(a, b);

型が一致しない場合、コンパイラは暗黙的な型変換を実行しようとします。変換が失敗すると、コンパイラはエラーを報告します

2.2.5 テンプレートパラメータのマッチング原則

同じ名前の非テンプレート関数と関数テンプレートが存在する場合、コンパイラは関数を呼び出すときに一致ルールに従って適切な関数を選択します。詳細は以下のとおりです。

  1. 非テンプレート関数を優先します。

    実際のパラメータの型と正確に一致する非テンプレート関数が存在する場合、コンパイラは、型の一致により特化しているため、この非テンプレート関数の呼び出しを優先します。
  2. テンプレートパラメータのマッチング:

    実際のパラメータの型に正確に一致する非テンプレート関数がない場合、コンパイラはテンプレート パラメータの一致を実行して、最も一致する関数テンプレートを見つけようとします。
  3. より良い一致:

    テンプレート関数がより適切に一致するインスタンスを生成できる場合、コンパイラーはこのテンプレート関数を呼び出すことを選択します。たとえば、暗黙的な型変換を通じてテンプレート パラメーターが実際のパラメーターとよりよく一致する場合、テンプレートが選択されます。

例を見てみましょう

#include <iostream>

// 通用输出函数模板
template<class T>
void Print(T value) {
    std::cout << value << std::endl;
}

// 重载的输出函数,专门处理 const char* 类型
void Print(const char* value) {
    std::cout << "String: " << value << std::endl;
}

int main() {
    Print(42);          // 调用通用函数模板 Print<T>
    Print("Hello");     // 调用重载函数 Print(const char*)
    Print(3.14);        // 调用通用函数模板 Print<T>
    Print<const char*>("Hello");    //显示调用通用函数模板 Print<T>

    return 0;
}
  1. template<class T> void Print(T value)は、さまざまなタイプのデータを出力するための汎用の出力関数テンプレートです。

  2. void Print(const char* value)const char*は、特に型を処理する出力関数のオーバーロードです。

  3. main関数では、Print(42)型が一致するため、呼び出しは汎用関数テンプレートと一致します。

  4. より特殊化されているため、呼び出しはPrint("Hello")オーバーロードされた関数と一致しますconst char*

  5. 呼び出しはPrint(3.14)依然として汎用関数テンプレートと一致します。

  6. 呼び出しテンプレートを表示すると、Print<const char*>("Hello") は明らかに関数テンプレートを呼び出します。

もう一つの例

#include <iostream>

// 专门处理 int 的加法函数
int Add(int left, int right) {
    return left + right;
}

// 通用加法函数模板
template<class T1, class T2>
T1 Add(T1 left, T2 right) {
    return left + right;
}

void Test() {
    Add(1, 2);       // 调用非函数模板,与 int Add(int left, int right) 匹配
    Add(1, 2.0);     // 调用函数模板,生成更匹配的版本
}
  1. int Add(int left, int right)intは、型を特に扱う追加関数です。

  2. template<class T1, class T2> T1 Add(T1 left, T2 right)は、さまざまなタイプのデータに適用できる汎用の加算関数テンプレートです。

  3. を呼び出すときAdd(1, 2)、コンパイラは、実際の引数の型と正確に一致する非関数テンプレートを呼び出すことを選択します。

  4. を呼び出すときAdd(1, 2.0)、コンパイラは関数テンプレートを呼び出すことを選択します。関数以外のテンプレートも一致する可能性がありますが、関数テンプレートはより一致するバージョンを生成でき、コンパイラはAdd実際のパラメータに基づいてより一致する関数を生成します。

2.3 クラステンプレート

2.3.1 コンセプト

クラス テンプレートに関しては、さまざまなデータ型に基づいてさまざまなクラスを生成できるテンプレートを作成することになります。クラス テンプレートを使用すると、型ごとに個別のコードを記述することなく、複数のデータ型に適用される汎用クラス定義を作成できます。

2.3.2 構文

クラス テンプレートの構文は関数テンプレートの構文に似ていますが、クラスの定義に適用されます。単純なクラス テンプレートの例を次に示します。

template <class T>
class MyContainer {
private:
    T value;

public:
    MyContainer(T val) : value(val) {}

    T GetValue() {
        return value;
    }
};

2.3.3 例

int main() {
    MyContainer<int> intContainer(42);
    MyContainer<double> doubleContainer(3.14);

    std::cout << intContainer.GetValue() << std::endl;    // 输出: 42
    std::cout << doubleContainer.GetValue() << std::endl; // 输出: 3.14

    return 0;
}

2.3.4 注意事項

  1. メンバー関数の定義:クラス テンプレートのメンバー関数も通常、テンプレート クラス内で定義する必要があります。それ以外の場合は、キーワードをtemplateテンプレートの宣言と実装の定義の外で使用する必要があります。

  2. テンプレート パラメーターの推定:クラス テンプレートをインスタンス化するときに、コンパイラーはテンプレート パラメーターの型を自動的に推定したり、インスタンス化に明示的に指定されたメソッドを使用したりできます。

  3. テンプレートの特殊化:関数テンプレートのテンプレートの特殊化と同様に、クラス テンプレートを特殊化して、特定の型のカスタム実装を提供することもできます。

  4. インスタンス化時のコード生成:クラス テンプレートがインスタンス化されると、コンパイラーは実際の型パラメーターに基づいて対応するクラス定義を生成し、それによって特定の型のクラスを作成します。

2.3.5 解釈上の注意

1. メンバー関数の定義:

クラス テンプレートでは、メンバー関数をテンプレート クラス内で定義でき、templateキーワードを使用してクラスの外で宣言および実装することもできます。

template <class T>
class MyContainer {
private:
    T value;

public:
    MyContainer(T val) : value(val) {}

    T GetValue() {
        return value;
    }
};

// 在类外部定义成员函数模板
template <class T>
T MyContainer<T>::GetValue() {
    return value * 2;
}

2. テンプレートパラメータの導出:

コンパイラーはクラス テンプレートをインスタンス化するときに、テンプレート パラメーターの型を自動的に推定することも、インスタンス化に明示的に指定されたメソッドを使用することもできます。

int main() {
    MyContainer intContainer(42);
    MyContainer<double> doubleContainer(3.14);

    std::cout << intContainer.GetValue() << std::endl;    // 输出: 42
    std::cout << doubleContainer.GetValue() << std::endl; // 输出: 3.14

    return 0;
}

3. テンプレートの特化:

関数テンプレートのテンプレート特殊化と同様に、クラス テンプレートを特殊化して、特定の型のカスタム実装を提供することもできます。

// 类模板定义
template <class T>
class MyContainer {
private:
    T value;

public:
    MyContainer(T val) : value(val) {}

    T GetValue() {
        return value;
    }
};

// 类模板的特化版本
template <>
class MyContainer<int> {
private:
    int value;

public:
    MyContainer(int val) : value(val) {}

    int GetValue() {
        return value * 2; // 自定义的实现
    }
};

3. テンプレートファイルの書き込み(特殊)

通常のコード ファイルの書き込みとは異なり、テンプレート ファイルの書き込みには、テンプレートの正しいインスタンス化とリンクを確保するための特別な予防措置と方法が必要です。テンプレートをヘッダー ファイルとソース ファイルに分割する方法の簡単な例を示します。

例:

メンバー関数を含むクラス テンプレート がありMyTemplate、それをヘッダー ファイルとソース ファイルに分割したいとします。

MyTemplate.h (ヘッダー ファイル):

#ifndef MYTEMPLATE_H
#define MYTEMPLATE_H

template <typename T>
class MyTemplate {
public:
    MyTemplate(T value);
    void PrintValue();
    
private:
    T data;
};

#endif

MyTemplate.cpp (ソース ファイル):

#include <iostream>
#include "MyTemplate.h"

template <typename T>
MyTemplate<T>::MyTemplate(T value) : data(value) {}

template <typename T>
void MyTemplate<T>::PrintValue() {
    std::cout << "Value: " << data << std::endl;
}

// 显式实例化模板
template class MyTemplate<int>;
template class MyTemplate<double>;

main.cpp (メインファイル):

#include "MyTemplate.h"

int main() {
    MyTemplate<int> intObj(42);
    MyTemplate<double> doubleObj(3.14);
    
    intObj.PrintValue();
    doubleObj.PrintValue();
    
    return 0;
}

ソース ファイル ではMyTemplate.cppコンストラクターとメンバー関数の定義を含むクラス テンプレートの実装を提供します。また、ソース ファイル内で明示的なインスタンス化(template class MyTemplate<int>;およびtemplate class MyTemplate<double>;) を使用して、コンパイル中に特定のタイプのテンプレート インスタンスが生成されるようにします。

最後に、メイン ファイルにmain.cppヘッダー ファイルをインクルードしMyTemplate.h、クラス テンプレートを使用するだけです。コンパイル時に、コンパイラはクラス テンプレートの実装部分MyTemplate.cppを。

この方法でテンプレートを別のファイルに記述するには追加の手順が必要になりますが、コードがより整理され、保守しやすくなります。

さらに詳しい説明

明示的なインスタンス化はソース ファイルMyTemplate.cppで使用され、コンパイラがコンパイル中に特定の型のテンプレート インスタンス化コードを生成するようにします。関数テンプレートの定義は通常、ヘッダー ファイルに配置されますが、C++ の別個のコンパイル モデルにより、コンパイラが必要なときにテンプレートをインスタンス化できるように、テンプレートの実装は同じコンパイル単位に存在する必要があります。

クラス テンプレートの実装では、テンプレート パラメーターがさまざまなタイプである可能性があるため、必要に応じてテンプレートが明示的に使用されない限り、コンパイラーは各タイプのインスタンス化コードを自動的に生成しません。これが、明示的なインスタンス化が使用される理由です。リンク時に正しいテンプレート実装を確実に見つけられるように、特定の型のインスタンス化コードを生成するようにコンパイラーに指示します。

同じ原則が、関数を別のファイルに記述する場合にも当てはまります。

これで関数テンプレートの基本は終わりです。著者は高度なテンプレートのチュートリアルを更新し続けます。

おすすめ

転載: blog.csdn.net/weixin_57082854/article/details/132178260