スパイス  組み込み制御装置の受注製作

自社標準仕様の製作
平成27年12月 1日

 汎用の高速シリアル通信機能(23)  C++のハマリ所(3) テンプレート関数

 3. テンプレート
 テンプレート機能については、まだあまり使っていない状態です。ここに書いた以外にも初心者が陥りやすい点があるかもしれません。

 3.1 テンプレートの型を自動的に決定できない場合
 テンプレートでは、文脈からテンプレートの型を自動的に決定できる場合と出来ない場合があります。
 以下に具体的な例を示します。
//文脈からテンプレートの型を決定できる場合
//ヘッダファイルにある定義部
template<typename T>
void calcDiff(const T& s, const T& e, T& rslt) const;

//.cppファイルにある関数呼び出し部
    TMR16_t start, end, diff;
    calcDiff(start, end, diff);    //文脈から型TはTMR16_tと判断できる

//文脈からテンプレートの型を決定できない場合
//ヘッダファイルにある定義部
template<typename T1, typename T2>
T1& cmp(const T2& s1, const T2& s2); 

//.cppファイルにある関数呼び出し部
    short ret;
    TMR16_t s1, s2;
    ret = cmp(const T2& s1, const T2& s2);    //文脈から型T2はTMR16_tと判断できるが、T1の型は決めきれない。
 後者の例ではT1の型を特定できません。short型に代入できるので数値型のどれかであることまでしか予測できず、最終的な決定が出来ません。
従って、このままならコンパイルエラーになります。
このように全てのテンプレート型を決定できないテンプレート関数の呼び出しでは、呼び出しの時にその型を"<>"で指定します。
    ret = cmp<short, TMR16_t>(const T2& s1, const T2& s2);    //テンプレートの型を指定した関数呼び出し
 文脈から全てのテンプレート型を決定出来る場合でも、上記のようにその型を指定することは可能です。ただし、両者に矛盾があるとコンパイルエラーになります。

 3.2 テンプレート関数の定義部がヘッダファイルに無い場合
 大半のC++入門クラスの説明ではテンプレート関数はヘッダファイルにインライン関数として定義されています。
しかし、関数が大きい場合にはヘッダファイルに置くのは不適当です。ヘッダファイルに記述された関数はインライン関数の扱いです。本当にインライン展開されると、その分メモリを圧迫します。
このため、ヘッダファイルにはプロトタイプ宣言のみを記述し、関数定義本体はソースファイルに記述するようにします。というかむしろこの方が本来の関数定義のやり方です。するとコンパイルは通るのですがリンク時にエラーとなります。エラ−メッセージは”指定されたオブジェクトが無い”といった内容です。
これは少し考えると見当がつきます。関数の本体がヘッダーファイルからソースファイルに移動したために、コンパイルの段階ではテンプレートの型をどのように設定してオブジェクトを生成すればいいかは判断できません。従って、具体的なオブジェクトの生成は行われずコンパイルが終了します。
その後、リンクの段階で初めて具体的なテンプレートの型を含めたオブジェクトが要求されるので、”そんなものは無い”という結末になります。
ヘッダファイル内のインライン関数なら必要なテンプレートの型が決まった後から、その型のインライン関数を定義できます
(H27/12/01追記)
コンパイル中に全てのテンプレートの型が決まるので問題が起きません。
なぜならコンパイラの動作中に全てのテンプレート関数の呼び出しを確認し、その型のテンプレートオブジェクトを生成することができる。
上記の訂正部分の説明が不適当でした。
(追記ここまで)
 では、どうすればソースファイルのコンパイル段階で欲しいテンプレート型のオブジェクトを生成することが出来るか?
コンパイル段階で必要なテンプレートの型に関する情報が必要であることは明白です。それをどのように記述すればいいかが問題です。
明示的インスタンス化と呼ぶようですが、下記のようにテンプレート型を指定した宣言を追加します。
//.cppファイル
template<typename T>
void func(T value)
{
........
}
//関数定義の後に必要なテンプレート型を全て明示する
typename void func<int>(int value);
typename void func<unsigned int>(unsigned int value);

目次へ  前へ  次へ