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

テンプレートのインスタンス化とは、関数テンプレートまたはクラス テンプレートから具象関数または具象クラスを作成することです。テンプレートのインスタンス化の作成は、暗黙的 (コンパイラー生成) または明示的 (ユーザー提供) にすることができます。

特定のテンプレート引数のテンプレートが必要な場合は、コンパイラがテンプレートを自動生成します。場合によっては、ヘッダー ファイルからテンプレート定義を削除したい場合や、計算能力を消費するテンプレートのインスタンス化を避けたい場合があります。この場合、明示的なインスタンス化が役に立ちます。

暗黙のインスタンス化

暗黙的なインスタンス化をデフォルトで選択する必要があります。暗黙的なインスタンス化とは、提供されたテンプレート引数の具体的な関数またはクラスをコンパイラが自動的に生成することを意味します。一般に、コンパイラは関数の引数からテンプレート引数も推測します。 C++17 では、コンパイラはクラス テンプレートのテンプレート引数を推測することもできます。

// implicitTemplateInstantiation.cpp

#include <iostream>
#include <string>
#include <vector>

template <typename T>
class MyClass{
 public:
 MyClass(T t) { }
 std::string getType() const {
 return typeid(T).name();
 }
};

template<typename T>
bool isSmaller(T fir, T sec){
 return fir < sec;
}

int main(){

 std::cout << '\n';

 std::cout << std::boolalpha;
 
 std::vector vec{1, 2, 3, 4, 5}; // (1)
 std::cout << "vec.size(): " << vec.size() << '\n';
 
 MyClass myClass(5); // (2)
 std::cout << "myClass.getType(): " << myClass.getType() << '\n';
 
 std::cout << '\n';
 
 std::cout << "isSmaller(5, 10): " 
 << isSmaller(5, 10) << '\n'; // (3)
 std::cout << "isSmaller<double>(5.5f, 6.5): " 
 << isSmaller<double>(5.5f, 6.5) << '\n'; // (4)
 
 std::cout << '\n';
 
}

行 (1) と (2) は、クラス テンプレートの引数推定 (CTAG) を使用します。 std::vector または MyClass コンストラクターの引数からその型を推測できます。行 (3) は、そのテンプレート引数も推定します。逆に (4) 行では、テンプレート引数 double 明示的に指定されています:isSmaller<double>(5.5f, 6.5 ).

コンパイラは、暗黙的なテンプレートのインスタンス化ごとに、具体的な関数またはクラスを作成します。 C++Insights はこのプロセスを視覚化します。

この自動プロセスは非常に快適ですが、いくつかの欠点があります。

<オール>
  • テンプレートを暗黙的にインスタンス化する場合、通常、テンプレートの定義はヘッダー ファイルに表示されます。おそらく、定義を開示したくないでしょう。
  • 特定のテンプレート引数のテンプレートが必要な場合、具体的な翻訳単位で使用できない場合、コンパイラはインスタンス化します。翻訳単位は、C プリプロセッサの処理後のソース ファイルです。通常、リンカーは冗長なテンプレートのインスタンス化をすべて削除し、1 つを保持します。これは時間とスペースの無駄です。
  • どちらの問題も、テンプレートを明示的にインスタンス化することで解決できます。

    明示的なインスタンス化

    明示的なインスタンス化には 2 つの特徴があります。明示的なインスタンス化の定義と明示的なインスタンス化の宣言。

    • 明示的なインスタンス化定義の構文: template <template declaration>
    • 明示的なインスタンス化宣言の構文: extern template <template declaration>

    構文を調べると、キーワード extern

    明示的なテンプレートのインスタンス化とは、テンプレートの定義を生成することを意味します。以下は簡単な例です。

    // explicitTemplateInstantiation.cpp
    
    #include <iostream>
    #include <string>
    #include <vector>
    
    template <typename T>
    class MyClass{
     public:
     MyClass(T t) { }
     std::string getType() const {
     return typeid(T).name();
     }
    };
    
    template<typename T>
    bool isSmaller(T fir, T sec){
     return fir < sec;
    }
     
    template class std::vector<int>; // (1)
    template bool std::vector<double>::empty() const; // (2)
    
    template class MyClass<int>; // (3)
    template std::string MyClass<double>::getType() const; // (4)
    
    template bool isSmaller(int, int); // (5)
    template bool isSmaller<double>(double, double); // (6)
    
    int main(){
    
     std::cout << '\n';
     
     std::cout << std::boolalpha;
     
     std::vector vec{1, 2, 3, 4, 5};
     std::cout << "vec.size(): " << vec.size() << '\n';
     
     MyClass myClass(5);
     std::cout << "myClass.getType(): " << myClass.getType() << '\n';
     
     std::cout << '\n';
     
     std::cout << "isSmaller(5, 10): " 
     << isSmaller(5,10) << '\n';
     std::cout << "isSmaller<double>(5.5f, 6.5): " 
     << isSmaller<double>(5.5f, 6.5) << '\n';
     
     std::cout << '\n';
     
    }
    

    行 (1) から (6) は興味深いものです。キーワード template のおかげで 、明示的な template インスタンス化が行われます。

    • 行 (1) で std::vector を明示的にインスタンス化 int の場合 行 (2) そのメンバー関数 empty double. の場合
    • 行 (3) は MyClass を明示的にインスタンス化します int の場合 行 (4) そのメンバー関数 getType double の場合 .
    • 行 (5) で明示的に isSmaller をインスタンス化 (int, int) の場合 、および行 (6) は (double, double) に対して同じことを行います 明示的なテンプレート引数 double を提供する .

    テンプレートの実装を非表示

    明示的なテンプレートのインスタンス化は、テンプレートの定義を隠すのにどのように役立ちますか?

    • テンプレート宣言をヘッダー ファイルに入れます。
    • テンプレート定義をソース ファイルに入れます。ソース ファイルの最後でテンプレートを明示的にインスタンス化します。
    • ヘッダー ファイルを含めてテンプレートを使用します。

    このプロセスを例示する 3 つのファイルを次に示します。

    • テンプレート宣言
    // MyClass.h
    
    #include <typeinfo>
    #include <string>
    
    template <typename T>
    class MyClass{
     public:
     MyClass(T t) { }
     std::string getType() const;
    };
    

    • int のテンプレート定義と明示的なインスタンス化
    // MyClass.cpp
    
    #include "MyClass.h"
    
    template <typename T>
    std::string MyClass<T>::getType() const {
     return typeid(T).name();
    }
    
    template class MyClass<int>; 
    

    • テンプレートの使用
    // mainMyClass.cpp
    
    #include "MyClass.h"
    #include <iostream>
    
    int main() {
    
     std::cout << '\n'; 
    
     MyClass myClass(5);
     std::cout << "myClass.getType(): " << myClass.getType() << '\n';
    
     /*
     MyClass myClass2(5.5);
     std::cout << "myClass2.getType(): " << myClass2.getType() << '\n';
     */
    
     std::cout << '\n';
    
    }
    

    プログラムをコンパイルして実行すると、期待どおりの結果が得られます。

    しかし、 MyClass を使おうとすると int 以外のタイプの場合 、リンカーエラーが発生します。これは、コメントアウトされた行を使用したときに表示されるリンカー エラー メッセージです。

    double のテンプレートのインスタンス化はありません

    テンプレートのインスタンス化を抑制する

    MyClass<int を使用するとします。> リンカーがまとめたさまざまな翻訳単位で。基本的に、リンカーはテンプレートのインスタンス化を 1 つを除いてすべて破棄します。これは計算時間の無駄です。 C++11 での extern キーワードの使用のおかげで、明示的なテンプレートのインスタンス化定義から明示的なテンプレートのインスタンス化宣言を作成できます。

    template class MyClass<int>; // explicit instantiation definition
    extern template class MyClass<int>; // explicit instantiation declaration 
    

    重要な観察事項は、2 行目ではテンプレートのインスタンス化が発生しないことです。これは、リンカーが破棄するものではなく、コンパイラが生成することを意味します。 MyClass<int> の 1 つのインスタンス化を確実にする必要があるだけです。 利用可能なリンカー用です。そうでない場合、リンカー エラーが発生します。

    次は?

    このより技術的な投稿の後、次の投稿で可変個引数テンプレートについて書きます ... .