web-dev-qa-db-ja.com

「未定義の参照」テンプレートクラスコンストラクター

私はすべてが適切に宣言され定義されていると思うので、なぜこれが起こっているのか分かりません。

テンプレートを使用して設計された次のプログラムがあります。これはキューの単純な実装であり、メンバー関数「追加」、「減算」、および「印刷」を備えています。

「nodo_colaypila.h」でキューのノードを定義しました。

#ifndef NODO_COLAYPILA_H
#define NODO_COLAYPILA_H

#include <iostream>

template <class T> class cola;

template <class T> class nodo_colaypila
{
        T elem;
        nodo_colaypila<T>* sig;
        friend class cola<T>;
    public:
        nodo_colaypila(T, nodo_colaypila<T>*);

};

次に、「nodo_colaypila.cpp」の実装

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

template <class T> nodo_colaypila<T>::nodo_colaypila(T a, nodo_colaypila<T>* siguiente = NULL)
{
    elem = a;
    sig = siguiente;//ctor
}

その後、キューテンプレートクラスとその機能の定義と宣言:

「cola.h」:

#ifndef COLA_H
#define COLA_H

#include "nodo_colaypila.h"

template <class T> class cola
{
        nodo_colaypila<T>* ult, pri;
    public:
        cola<T>();
        void anade(T&);
        T saca();
        void print() const;
        virtual ~cola();

};


#endif // COLA_H

「cola.cpp」:

#include "cola.h"
#include "nodo_colaypila.h"

#include <iostream>

using namespace std;

template <class T> cola<T>::cola()
{
    pri = NULL;
    ult = NULL;//ctor
}

template <class T> void cola<T>::anade(T& valor)
{
    nodo_colaypila <T> * nuevo;

    if (ult)
    {
        nuevo = new nodo_colaypila<T> (valor);
        ult->sig = nuevo;
        ult = nuevo;
    }
    if (!pri)
    {
        pri = nuevo;
    }
}

template <class T> T cola<T>::saca()
{
    nodo_colaypila <T> * aux;
    T valor;

    aux = pri;
    if (!aux)
    {
        return 0;
    }
    pri = aux->sig;
    valor = aux->elem;
    delete aux;
    if(!pri)
    {
        ult = NULL;
    }
    return valor;
}

template <class T> cola<T>::~cola()
{
    while(pri)
    {
        saca();
    }//dtor
}

template <class T> void cola<T>::print() const
{
    nodo_colaypila <T> * aux;
    aux = pri;
    while(aux)
    {
        cout << aux->elem << endl;
        aux = aux->sig;
    }
}

次に、これらの機能を次のようにテストするプログラムがあります。

「main.cpp」

#include <iostream>
#include "cola.h"
#include "nodo_colaypila.h"

using namespace std;

int main()
{
    float a, b, c;
    string d, e, f;
    cola<float> flo;
    cola<string> str;

    a = 3.14;
    b = 2.71;
    c = 6.02;
    flo.anade(a);
    flo.anade(b);
    flo.anade(c);
    flo.print();
    cout << endl;

    d = "John";
    e = "Mark";
    f = "Matthew";
    str.anade(d);
    str.anade(e);
    str.anade(f);
    cout << endl;

    c = flo.saca();
    cout << "First In First Out Float: " << c << endl;
    cout << endl;

    f = str.saca();
    cout << "First In First Out String: " << f << endl;
    cout << endl;

    flo.print();
    cout << endl;
    str.print();

    cout << "Hello world!" << endl;
    return 0;
}

しかし、ビルドすると、コンパイラはテンプレートクラスのすべてのインスタンスでエラーをスローします。

`cola(float):: cola() '...への未定義の参照(実際はcola' <'float'> ':: cola()ですが、これを使用させませんそのように。)

等々。合計で17の警告。プログラムで呼び出されているメンバー関数の警告をカウントします。

どうしてこれなの?これらの関数とコンストラクターは定義されていました。コンパイラーは、テンプレート内の「T」を「float」、「string」などに置き換えられると考えました。それがテンプレートを使用する利点でした。

何らかの理由でヘッダーファイルに各関数の宣言を配置する必要があることをここで読みました。そうですか?もしそうなら、なぜですか?

前もって感謝します。

126
Heathcliff

ヘッダーファイル内で関数を定義する必要があります。
テンプレート関数の定義をソースファイルに、宣言をヘッダーファイルに分離することはできません。

テンプレートがそのインスタンス化をトリガーする方法で使用される場合、コンパイラはその特定のテンプレート定義を確認する必要があります。これが、テンプレートが宣言されるヘッダーファイルで定義されることが多い理由です。

参照:
C++ 03標準、§14.7.2.4:

定義非エクスポート関数テンプレート、非エクスポートメンバー関数テンプレート、または非エクスポートメンバー関数またはクラステンプレートの静的データメンバーすべての翻訳に存在するものとする) unit明示的にインスタンス化されます。

編集:
コメントに関する議論を明確にするには:
技術的には、このリンクの問題を回避するには3つの方法があります。

  • 定義を.hファイルに移動するには
  • .cppファイルに明示的なインスタンス化を追加します。
  • #includeテンプレートを使用して.cppファイルでテンプレートを定義する.cppファイル。

それぞれに長所と短所があり、

定義をヘッダーファイルに移動すると、コードサイズが増加する可能性があります(現代のコンパイラはこれを回避できます)が、コンパイル時間は確実に増加します。

明示的なインスタンス化アプローチの使用は、従来のマクロのようなアプローチに戻りつつあります。別の欠点は、プログラムでどのテンプレートタイプが必要かを知る必要があることです。単純なプログラムの場合、これは簡単ですが、複雑なプログラムの場合、事前に決定するのは困難になります。

Cppファイルを含めることは混乱を招くと同時に、上記の両方のアプローチの問題を共有します。

私は最初の方法が最も簡単に実行できるため、それを使用することを推奨しています。

11
Alok Save

このリンクは、どこがおかしいのかを説明しています。

[35.12]なぜテンプレートクラスの定義をその宣言から分離して、.cppファイル内に配置できないのですか?

コンストラクタ、デストラクタメソッド、その他の定義をヘッダーファイルに配置すると、問題が修正されます。

これは別のソリューションを提供します:

テンプレート関数でリンカーエラーを回避するにはどうすればよいですか?

ただし、これにはテンプレートの使用方法を予測する必要があり、一般的な解決策として直感に反します。何らかの内部メカニズムで使用されるテンプレートを開発し、その使用方法を規制したい場合でも、それは解決します。

4
Liam M