汎用モジュールはどのようにリンクされますか?
以下のファイル内
// func.hpp
template<typename T>
T const& func(T const &v);
// func.cpp
template<typename T>
T const& func(T const &v)
{
return v;
}
コンパイルすると、次のように生成されますerror
undefined reference to `int const& func<int>(int const&)'
コンパイル時に、コンパイラは関数テンプレートの実装func.cpp
のみを読み取りfunc
、関数テンプレート インスタンスを生成する必要があるステートメントを読み取らないため、関数func
インスタンスは生成されません。
をコンパイルする場合main.cpp
、関数テンプレート インスタンスが使用されますが、ヘッダ ファイルmain.cpp
のみfunc.hpp
がインクルードされ、後者はfunc
特定の関数実装のない単なる関数テンプレートの宣言であるため、func
現時点ではコンパイラは関数モジュール インスタンスを生成できません。呼び出しリンクでは、最終的なリンク プロセス中に関数の実装が見つかることを期待していましたが、その実装が存在しないため、リンクは失敗しました
この時点で、明示的に生成された関数テンプレートを使用できます。
// func.cpp
template<typename T>
T const& func(T const &v)
{
return v;
}
template int const& func(int const &v); // 明确生成模板实例 指示编译器根据此函数声明寻找合适的模板实现
template
キーワードの後にテンプレート パラメーター リストがなく、関数宣言がある場合は、コンパイラーがこの関数宣言に基づいて適切なテンプレート実装を見つけるように指示されていることを意味します。
この時点で関数は として宣言されています
T=int
が、これは単なる一時しのぎです。したがって、テンプレートの実装もヘッダー ファイルに配置する必要があります。このとき、テンプレートの実装は他のコード ファイルに直接含めることができます。テンプレート インスタンスを生成する必要がある場合、コンパイラはそれをヘッダー ファイル上で生成できます。既知のテンプレート実装に基づいてスポット化するため、他のターゲット ファイルで生成されたテンプレート インスタンスに依存する必要がなくなります。
テンプレートのエクスポート
C++98
テンプレート コードを整理する別の方法 -Export Template
外部名テンプレート (同じヘッダー ファイルに定義と実装を配置するテンプレートを内部名テンプレートと呼ぶことができます) を提供します。
// func.hpp
export template<typename T>
T const& func(T const &v);
// func.cpp
export template<typename T>
T const& func(T const &v)
{
return v;
}
C++
外国名テンプレートをサポートする現在公表されているコンパイラには、次のComeau C++
ものがあります。Borland C++
クラステンプレートのいくつかの使用法
クラス前のテンプレートの生成
template<typename T> class my_class; // 前置类模板生成
クラステンプレート内に実装されたメンバー関数テンプレート
template<typename T=int>
class my_class
{
// 在类模板内实现的成员函数模板
void push(T const &v)
{
std::cout << "push" << std::endl;
}
}
メンバー関数宣言。クラス テンプレートの外部で実装されます。
template<typename T=int>
class my_class
{
// 成员函数声明,将在类模板外实现
void pop();
};
クラステンプレートの外に実装されたメンバー関数テンプレート
template<typename T>
void my_class<T>::pop()
{
std::cout << "pop" << std::endl;
}
メンバー関数テンプレート: 通常のクラスのメンバー関数テンプレートは、クラス内でその場で実装することも、クラス外で個別に実装することもできます。
struct normal_class
{
template<typename T>
void set(T const &v) {
value = int(v);}
template<typename T>
T get();
};
template<typename T>
T normal_class::get()
{
return T(value);
}
クラス テンプレートのメンバー関数には、追加のテンプレート パラメーターを持つこともできます
template<typename T0>
struct normal_class
{
template<typename T1>
T1 get();
};
template<typename T0> template<typename T1>
T1 normal_class<T0>::get()
{
return T1(value);
}
テンプレートパラメータのタイプ
typename
一般に、テンプレート パラメーターはとでマークされておりclass
、関数およびクラス テンプレートの実装では、テンプレート パラメーターは特定の型を参照するために使用されます。したがって、これら 2 つのキーワードでマークされた型のテンプレート パラメーターは、型テンプレート パラメーターと呼ばれます。以下の4種類に加えて
- 整数と列挙型
- オブジェクトまたは関数へのポインタ
- オブジェクト関数への参照
- オブジェクトメンバーへのポインタ
上記の 4 つのパラメータは、非型テンプレート パラメータと総称されます。テンプレート パラメータ リストでの宣言方法は、対応する型の変数宣言と一致します。また、テンプレート パラメータはテンプレートであることもでき、これはテンプレート テンプレートと呼ばれますパラメータ。
非型テンプレート パラメーターの役割は、関数テンプレートまたはクラス テンプレートの一部の定数を事前定義することと同等です。テンプレート インスタンスを生成するときは、定数、つまりコンパイル時に既知の値を非型テンプレートに割り当てる必要もあります。テンプレートパラメータを入力します。
整数テンプレートパラメータ
同じアルゴリズムまたはクラスに対して異なる変数を定義する必要がある場合、非型テンプレート パラメーターを使用することが最適です。これにより、定数の違いによるコードの書き換えを回避できるだけでなく、既知の定数をそのまま実装することによる不都合も回避できます。変数。追加の運用オーバーヘッド
template<typename T, unsigned size>
class ay
{
T elems[size];
public:
T& operator[](unsigned i)
{
return elems[i];
}
};
ay<char, 2> ap{
}; // 调用
オブジェクトまたは関数へのポインタ
変更と不変の間で最適な実装を実現するには、通常、関数ポインタがコールバックとして使用されます。callback
template<typename T, void (*f)(T &v)>
void foreach(T array[], unsigned size)
{
for (unsigned i = 0; i < size; i ++)
{
f(array[i]);
}
}
template<typename T>
void inc(T &v)
{
++ v;
}
template<typename T>
void dec(T &v)
{
-- v;
}
template<typename T>
void print(T &v)
{
std::cout << v << ' ';
}
int arr[] = {
1, 2, 3, 4, 5
};
foreach<int, print<int>> (arr, 5);
foreach<int, inc<int>> (arr, 5);
foreach<int, print<int>> (arr, 5);
ポインターおよび参照テンプレートのパラメーター
わずかに
メンバー関数ポインター テンプレートのパラメーター
メンバー関数ポインターがテンプレート パラメーターとして使用される場合、その使用法は関数ポインター テンプレート パラメーターの場合と同様です。
int (some_class::* mfp)(int);
class some_value
{
private:
int value;
public:
explicit some_value(int _value): value(_value){
}
int add_by(int op){
return value += op;
}
int sub_by(int op){
return value -= op;
}
};
例外を除きmfp
、他の部分はポイントされる関数の詳細を記述するために使用されます。同じ型のメンバー関数ポインターを複数の場所に定義する必要がある場合は、最初に定義されたポインター型の別名を使用できます。この場合typedef
、それはsome_class_mfp
別名です。
typedef int (some_class:: *some_class_mfp)(int);
some_class_mfp mfp;
template<some_value_mfp func>
int call(some_value &va, int op)
{
return (va.*func)(op);
}
これはエイリアスで&some_value::add_by
あり、オブジェクトまたは関数へのポインターに似た型です。some_class_mfp
some_class_mfp
some_value v0(0);
cout << call<&some_value::add_by>(v0, 1) << endl;
メタプログラミングのヒント
さまざまな基本タイプの操作に対する再利用可能なコードのサポートを提供することは、C++
メタプログラミングの重要なトピックです
型操作の最も基本的な要件は、いくつかの型に基づいて別の型を推測することです (たとえば、コンテナ型が指定された場合はそのイテレータ型を取得し、イテレータ型が指定された場合はそのラベル タイプを検索します)。
C++
コンパイル時に既知のいくつかの要素から他のコンパイル時に既知の要素を導出できるメカニズムは、 メタ関数 と呼ばれます。
テンプレートはメタ関数の本体であり、テンプレート パラメーターはメタ関数のパラメーターであり、テンプレート内のネストされた定義はメタ関数の戻り値です。テンプレートを変更するときに特殊なケースがある場合は、これは、メタ関数に条件判定ロジックを実装することと同じです。
#if 模板实参匹配模板特例参数
@echo 特例中的类型
#else if
@echo 通例中的类型
通例
template<typename T0, typename T1>
struct is_same
{
enum {
result = 0
};
};
template<bool cond, typename Type_True, typename Type_false>
struct if_
{
typedef Type_True return_type;
};
特例
template<typename T>
struct is_same<T, T>
{
enum {
result = 1
};
};
template<typename Type_True, typename Type_false>
struct if_<false, Type_True, Type_false>
{
typedef Type_True return_type;
};
is_same
2 つの型が同じかどうかを判断するために使用されます。戻り値にはブール セマンティクスが必要であるため、メタ関数のif_
実装は条件に基づいて 2 つの型のいずれかを返します。
template<typename MV1, typename MV2, typename tag1, typename tag2>
struct return_type_of
{
typedef typename if_<is_same<tag1, matrix_tag>::result && is_same<tag2, vector_tag>::result,
MV2, MV2>::return_type return_type;
};
typename
この関数は明示的に呼び出して、コンパイラにメタ関数を「実行」するように指示します。そうしないと、ロジックが正しくなくなります。