非型テンプレートパラメータの使用方法と非型テンプレートパラメータを使用する際の注意が必要な事項とテンプレートのインスタンス化を制御してメモリスペースを節約する方法

非型テンプレートパラメータ

型以外のテンプレートパラメータで関数をオーバーロードする場合の注意事項

フォーム1:

#include <iostream>  
using namespace std;  
#include <vector>  
#include <algorithm>  
  
template <typename T, int val>  
T AddValue(T const& obj)  
{  
    return obj + val;  
}  
  
int main()  
{  
    vector<int> Vector_Obj1{ 1,2,3,4,5,6 }, Vector_Obj2;  
    Vector_Obj2.resize(Vector_Obj1.size());  
    transform(Vector_Obj1.begin(), Vector_Obj1.end(), Vector_Obj2.begin(), (int(*)(int const&))AddValue<int, 5>);  
    for_each(Vector_Obj2.begin(), Vector_Obj2.end(), [](const int& obj) {cout << obj << " "; });  
} 

 

フォーム2:

#include <iostream>  
using namespace std;  
#include <vector>  
#include <algorithm>  
  
template <typename T, int val>  
T AddValue(T const& obj)  
{  
    return obj + val;  
}  
  
int main()  
{  
    vector<int> Vector_Obj1{ 1,2,3,4,5,6 }, Vector_Obj2;  
    Vector_Obj2.resize(Vector_Obj1.size());  
    transform(Vector_Obj1.begin(), Vector_Obj1.end(), Vector_Obj2.begin(), AddValue<int, 5>);  
    for_each(Vector_Obj2.begin(), Vector_Obj2.end(), [](const int& obj) {cout << obj << " "; });  
}  

 

上記の2つの形式がありますが、最大の違いは次のとおりです。

①transform(Vector_Obj1.begin()、Vector_Obj1.end()、Vector_Obj2.begin()、(int(*)(int const&))AddValue <int、5>);

②transform(Vector_Obj1.begin()、Vector_Obj1.end()、Vector_Obj2.begin()、AddValue <int、5>);

時々質問があります。これら2つのコードの出力結果は同じですが、関数ポインターのタイプがフォーム1で強制変換されるのはなぜですか。

「C ++テンプレート」には、示唆に富む一節があります。

 

この一節の意味は次のとおりです。

AddValue <int、5>は、関数ポインターと同等です。同じ名前でパラメーターが異なる多くのC ++オーバーロード関数の場合、パラメータータイプではなく、独立した関数ポインターAddValueを確認してください。関数ポインターAddValueはわかりません。 all。関数のオーバーロードされたバージョンを指します。コンパイラーは、TransformのAddValue関数ポインターに渡されたパラメーターを監視および判断して、呼び出される関数のオーバーロードバージョンを判別できますが、それはわかりません。したがって、強制型変換を実行する必要があります。強制するかどうかは意味がありませんが、この関数がオーバーロードされたバージョンの関数を呼び出してデータの山を操作するまでは、少なくとも私たちの考えを明確にすることができます。

例えば:

#include <iostream>  
using namespace std;  
#include <vector>  
#include <algorithm>  
  
template <typename T, int val>  
T AddValue(T const& obj)  
{  
    return obj + val;  
}  
template <typename T, int val>  
double AddValue(double const& obj)  
{  
    return (obj + (double)val) / 2;  
}  
  
int main()  
{  
    vector<int> Vector_Obj1{ 1,2,3,4,5,6 };  
    vector<double> Vector_Obj2;  
    Vector_Obj2.resize(Vector_Obj1.size());  
    transform(Vector_Obj1.begin(), Vector_Obj1.end(), Vector_Obj2.begin(), (double(*)(double const&))AddValue<int, 5>);  
    for_each(Vector_Obj2.begin(), Vector_Obj2.end(), [](const double& obj) {cout << obj << " "; });  
}  

 

この例では、強制型変換が非常に必要です。強制型変換がないと、コンパイラーは、裸の関数ポインターに基づいて、どの関数オーバーロードバージョンを実行するかを単純に識別できません。コンパイラーが不確実であると思われる場合現象が発生すると、誤った警告が発生します。次々にフォローしてください:

 

非型テンプレートのパラメーター制限

浮動小数点型(可変/定数)の数量にすることはできません。

テンプレートの特殊化を照合する場合、コンパイラーは非型パラメーターを含むテンプレートパラメーターを照合します。

その性質上、浮動小数点値は正確ではなく、C ++標準はそれらの実装を指定していません。したがって、2つの浮動小数点非型パラメーターが実際にいつ一致するかを判別することは困難です。

template <float f> void foo () ;void bar () {

    foo< (1.0/3.0) > ();

foo< (7.0/21.0) > ();

}

これらの式は必ずしも同じ「ビットパターン」を生成するわけではないため、同じ特殊化を使用することを保証することは不可能です。これをカバーする特別な表現はありません。

②非型テンプレートパラメータは、内部リンクオブジェクト(文字列など)を実パラメータとして使用できません。

同じ文字列文字列を使用してプロジェクトの複数のソースファイルで非型パラメータテンプレートをインスタンス化する場合、同じ文字列文字列によってインスタンス化される非型パラメータテンプレートは同じである必要がありますが、忘れないでください。異なるソースファイルで非型パラメータテンプレートをインスタンス化します。異なるソースファイルの文字列定数は異なる領域に格納されます。異なる領域のデータからインスタンス化された非型パラメータテンプレートはコンパイラ用ではありません。同じですが、これは少しです。怖い。私たちの意見では、同じ文字列文字列は同じテンプレートでインスタンス化する必要がありますが、同じ文字列文字列の定義ファイルが異なるため、最終的にインスタンス化されるテンプレートは異なります。この場合、コンパイラは「 -タイプのテンプレートパラメータは、内部リンクオブジェクトを使用できません。」

文字列型を非型テンプレートパラメータとして使用するにはどうすればよいですか?(リンクプロパティの変更)

C ++は、constによって変更された変数は変更できないだけでなく、このファイルでのみ表示される内部リンクプロパティも持つことを規定しています。(これは元のC言語の静的修飾子の機能であり、現在constにもこの機能があります。)extern constを変更と組み合わせると、externがconstの内部リンク属性を抑制することも追加されています。したがって、extern const文字列には引き続き外部リンクプロパティがありますが、変更することはできません。

showing.hpp

#include <iostream>  
#include <string>  
using namespace std;  
  
template <const string& obj>  
void ShowInf()  
{  
    cout << obj << endl;  
}  

 

main.cpp

#include <iostream>  
#include "ShowInf.hpp"  
using namespace std;  
  
extern const string str = "hello world!";  
  
int main()  
{  
    ShowInf<str>();  
}

​​​​​​​  

出力結果:

 

注:externは、main.cppの関数本体の外部にのみ存在できます。次の状況は間違っています。

#include <iostream>  
#include "ShowInf.hpp"  
using namespace std;  
    
int main()  
{  
    extern const string str = "hello world!";      
    ShowInf<str>();  
}  

 

Const char *変数が機能しないのはなぜですか?

次の方法でconstchar配列を渡す必要があります。

Main.cpp

#include <iostream>  
#include <string>  
#include "Person.hpp"  
#include "ShowInf.hpp"  
using namespace std;  
  
const char s[] = "hello";  // 加不加extern都OK
  
int main()  
{  
    ShowInf<s>();  
}  

 

ShowInf.hpp

#include <iostream>  
#include <string>  
using namespace std;  
  
template <const char* obj>  
void ShowInf()  
{  
    cout << obj << endl;  
}  

 

注:変数sは、const char * s = "hello"ではなく、const char s [] = "hello"として宣言する必要があります。

上記のように、クラス型を非型テンプレートの引数として使用することは可能ですか?

私は次のことを試しましたが、答えは「いいえ」でした。

クラス型はコンパイル時の定数として使用できないためです。実際、これを巧みに覚えておくことができます。クラス型は、複数の基本データ型の有機的な組み合わせです。浮動小数点型と倍精度浮動小数点型は、型以外のテンプレートのパラメーターとして使用できません。クラス型がこれほど大きくなるにはどうすればよいでしょうか。仕事!

Person.hpp

#include <iostream>  
#include <string>  
using namespace std;  
  
class Person  
{  
private:  
    int age;  
    string name;  
public:  
    Person(int age, string name)  
    {  
        this->age = age;  
        this->name = name;  
    }  
    void Define(int age, string name)  
    {  
        this->age = age;  
        this->name = name;  
    }  
    friend ostream& operator << (ostream& ostr, Person& obj);  
};  
  
ostream& operator << (ostream& ostr, Person& obj)  
{  
    ostr << obj.name << "的姓名为" << obj.age;  
    return ostr;  
}  

 

showing.hpp

#include <iostream>  
#include <string>  
using namespace std;  
  
template <const Person& obj>  
void ShowInf()  
{  
    cout << obj << endl;  
}  

 

Main.cpp

#include <iostream>  
#include <string>  
#include "Person.hpp"  
#include "ShowInf.hpp"  
using namespace std;  
  
extern  Person& obj;  
  
int main()  
{  
    obj.Define(19, "张三");  
    ShowInf<obj>();  
}

​​​​​​​  

出力エラー

 

内部リンクと外部リンクとは何ですか?

内部リンケージとは、シンボルがコンパイルユニットでのみ有効であり、他のコンパイルユニットからは見えないことを意味します。したがって、複数のコンパイルユニットが同じシンボルを持つことができます。const変数は内部リンクであるため、競合することなく複数の.cppファイルに表示できます。

外部リンクとは、他のコンパイルユニットが現在のコンパイルユニットのシンボルを参照できることを意味します。同じ外部リンクシンボルがある場合、リンク中にシンボル再定義エラーが報告されます。

パラメータがタイプのテンプレートの場合、同じタイプでインスタンス化されたテンプレートは、それらが属するファイルが異なるため、異なるストレージ領域に格納されます。これは、貴重なメモリの非常に傲慢な浪費です。この贅沢をどのように回避する必要がありますか。状況?

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

テンプレートが使用されている場合にのみインスタンス化されます。この機能は、同じインスタンスが複数のオブジェクトファイルに表示される可能性があることを意味します。たとえば、2つ以上の独立してコンパイルされたソースファイルが同じテンプレートを使用し、同じテンプレートパラメータを提供する場合、各ファイルには、テンプレートがパラメータを適用するインスタンスがあります。

上記の問題は、小さなプログラムでは大した問題ではありませんが、大きなプログラムでは、同じテンプレートを複数のファイルにインスタンス化することによる追加のオーバーヘッドが非常に深刻になる可能性があります。新しい標準では、明示的なインスタンス化を制御することで、このオーバーヘッドを回避できます。

インスタンス化を制御する

複数のファイルで変数を宣言するのと同じように、キーワードexternを使用して、プログラムの他の場所でインスタンス化の非extern宣言があることを約束できます。インスタンス化を含む.oファイルをリンクする必要があるのは次の場合のみです。実行可能プログラムへのリンク。それだけです。したがって、キーワードexternを使用してテンプレートを宣言しても、このファイルにインスタンス化コードは生成されません。

次のように特定の操作を示します。

最初のファイルは主にテンプレートを定義するために使用されます(もちろん、ここでの最初のファイルはプロジェクトで定義された多くのテンプレートファイルを指します):

// Stack.hpp
#include <iostream>  
using namespace std;  
#include <vector>  
  
// 模板的缺省值  
template <class T, class CONT = vector<T> >  
class Stack  
{  
private:  
    CONT element;  
    int Size;  
public:  
    Stack();  
    Stack(T obj);  
    Stack(vector<T> obj);  
  
    void Push(T obj);  
    void Pop();  
    T& Top();  
    bool Empty();  
    int PrintSize();  
    T& At(int order);  
};  
  
template <class T, class CONT /*= vector<T> */>  
T& Stack<T, CONT>::At(int order)  
{  
    if (order >= this->Size)  
    {  
        throw out_of_range("over array range!");  
    }  
    return this->element.at(order);  
}  
  
template <class T, class CONT /*= vector<T> */>  
int Stack<T, CONT>::PrintSize()  
{  
    return this->Size;  
}  
  
template <class T, class CONT /*= vector<T> */>  
bool Stack<T, CONT>::Empty()  
{  
    return this->element.empty();  
}  
  
template <class T, class CONT /*= vector<T> */>  
T& Stack<T, CONT>::Top()  
{  
    if (this->Size == 0)  
    {  
        throw out_of_range("Stack is empty!");  
    }  
    return *(this->element.end());  
}  
  
template <class T, class CONT /*= vector<T> */>  
void Stack<T, CONT>::Pop()  
{  
    if (this->Size == 0)  
    {  
        throw out_of_range("Stack is empty!");  
    }  
    this->element.pop_back();  
    this->Size--;  
}  
  
template <class T, class CONT /*= vector<T> */>  
void Stack<T, CONT>::Push(T obj)  
{  
    this->element.push_back(obj);  
    this->Size++;  
}  
  
template <class T, class CONT /*= vector<T> */>  
Stack<T, CONT>::Stack(vector<T> obj)  
{  
    this->Size = 0;  
    this->element.clear();  
  
    this->element = obj;  
    this->Size = obj.size();  
}  
  
template <class T, class CONT /*= vector<T> */>  
Stack<T, CONT>::Stack(T obj)  
{  
    this->Size = 0;  
    this->element.clear();  
  
    this->element.push_back(obj);  
    this->Size++;  
}  
  
template <class T, class CONT /*= vector<T> */>  
Stack<T, CONT>::Stack()  
{  
    this->Size = 0; // 赋值前一定要初始化  
    this->element.clear();  
}  

 

2番目のファイルでは、主にプロジェクトで使用するすべてのインスタンス化テンプレートを定義します。

// TemplateBuild.cpp
#include "Stack.hpp"  
#include <iostream>  
#include <string>  
using namespace std;  
  
template Stack<string>; // 加不加template关键字都OK  
template Stack<int>;  

 

3番目のファイルでは、外部リンクを介して以前に定義されたインスタンス化テンプレートを使用できます。

// TemplateApplication.cpp
#include "Stack.hpp"  
#include <iostream>  
#include <string>  
using namespace std;  
  
extern template Stack<string>;  
  
int main()  
{  
    Stack<string> Stack_Obj1("张三");  
    cout << Stack_Obj1.At(0) << endl;  
}

​​​​​​​  

コンパイル時に、プロジェクト内の各.cppファイル(TemplateApplication.cppおよびTemplateBuild.cpp)は、対応する.oファイルにコンパイルされます。コンパイラーがコンパイルされた後の次のステップは、「externlinkキーワードextern」に従ってそれぞれをリンクすることです。 oプロジェクトで一緒に取得したファイル(TemplateApplication.oとTemplateBuild.o)を使用して、実行可能なファイルを生成します。

注:外部リンクを使用し、すでにインスタンス化されている外部テンプレートをリンクして使用するため、コンパイラーはテンプレートのすべてのメンバーをインスタンス化する必要があります。これは、通常のテンプレートのインスタンス化とは異なります(異なるテンプレートのインスタンス化では、どのメンバーが使用されるかだけです)。どのメンバーをインスタンス化するか)、テンプレートをインスタンス化するこのメソッドは、テンプレートのすべてのメンバーをインスタンス化します。

非型パラメーターテンプレートの正式なパラメーター要件

非型テンプレートパラメータは制限されており、通常は定数整数(列挙値を含む)または外部リンクオブジェクトへのポインタ/参照です。

テンプレートパラメータ:定数整数(int、short、long ...コンパイラが決定できる整数データ型)

#include <iostream>  
using namespace std;  
  
template <unsigned int N>  
void ShowInf()  
{  
    cout << N << endl;  
}  
  
int main()  
{  
    const int a = 10;  
    ShowInf<a>();  
}  

 

テンプレートパラメータ:外部リンクオブジェクトへのポインタ/参照

#include <iostream>  
using namespace std;  
  
template <typename T, T(*Func)(const T& val1,const T& val2) >  
T Operator(T para1, T para2)  
{  
    return Func(para1, para2);  
}  
  
int Add(const int& para1, const int& para2)  
{  
    return para1 + para2;  
}  
  
int main()  
{  
    int para1 = 9, para2 = 10;  
    cout << Operator<int, Add>(para1, para2) << endl;  
} 

​​​​​​​ 

上記のコードは、テンプレートパラメータが関数ポインタの場合のコード記述形式を示しています。

テンプレートパラメータ:テンプレート

#include <iostream>  
using namespace std;  
  
template<typename T>  
class Print  
{  
public:  
    void operator()(const T& val)  
    {  
        cout << val << " ";  
    }  
};  
  
template<typename T>  
class Inc  
{  
public:  
    void operator()(T& val)  
    {  
        val++;  
    }  
};  
  
template<typename T>  
class Dec  
{  
public:  
    void operator()(T& val)  
    {  
        val--;  
    }  
};  
  
template< typename T, template<typename T> class Func>  
void Foreach(T Array[], unsigned size)  
{  
    Func<T> FuncA;  
    for (int i = 0; i < size; i++)  
    {  
        FuncA(Array[i]); // []为数组索引符号  
    }  
    cout << endl;  
}  
  
int main()  
{  
    int Array[] = { 1,2,3,4,5,6,7,8,9,10 };  
    Foreach<int, Inc>(Array, 10);  
    Foreach<int, Print>(Array, 10);  
    Foreach<int, Dec>(Array, 10);  
    Foreach<int, Print>(Array, 10);  
}  

 

演算結果:

 

おすすめ

転載: blog.csdn.net/weixin_45590473/article/details/112586725