1. ホーム
  2. c++

[解決済み] テンプレートクラスのコンストラクタの「未定義参照」【重複】について

2022-04-29 01:04:52

質問

すべて適切に宣言し、定義しているつもりなのに、なぜこのようなことが起こるのか見当がつきません。

私は以下のようなプログラムをテンプレートで設計しています。これはキューの簡単な実装で、メンバ関数として "add"、 "substract"、 "print"が用意されています。

キュー用のノードは fine "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;
}

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

未定義参照 to `cola(float)::cola()'... (本当は cola'<'float'>'::cola() なのですが、これでは使い物になりません)。

といった具合に。プログラムの中で呼び出されているメンバ関数に対するものも含めると、全部で17の警告が表示されました。

これはなぜでしょうか?これらの関数やコンストラクタは定義されていたのに。コンパイラはテンプレート内の "T" を "float" や "string" などに置き換えることができると思ったのです。

ここのどこかで読んだのですが、各関数の宣言はなぜかヘッダーファイルに書いた方がいいらしいです。そうなのでしょうか?もしそうなら、それはなぜですか?

どのように解決するのですか?

これは、C++プログラミングでよくある質問です。これには2つの有効な答えがあります。どちらの答えにも利点と欠点があり、その選択は文脈に依存することになります。一般的な答えは、ヘッダーファイルにすべての実装を置くことですが、場合によっては、別のアプローチが適しています。選択はあなた次第です。

テンプレート内のコードは、コンパイラが知っている「パターン」に過ぎません。コンパイラはコンストラクタをコンパイルしません。 cola<float>::cola(...)cola<string>::cola(...) を強制的に実行させるまでです。そして、このコンパイルがコンストラクタである 少なくとも さもなければ、「未定義参照」エラーが発生します。(の他のメソッドにも適用されます)。 cola<T> もあります)。

問題の把握

この問題の原因は main.cppcola.cpp は、まず別々にコンパイルされます。で main.cpp の場合、コンパイラは 暗黙のうちに テンプレート・クラスをインスタンス化します。 cola<float>cola<string> なぜなら、これらの特定のインスタンス化されたものは main.cpp . 悪いニュースは、これらのメンバ関数の実装が main.cpp に含まれるどのヘッダーファイルにもありません。 main.cpp そのため、コンパイラはこれらの関数の完全版を main.o . をコンパイルするとき cola.cpp のインスタンスは暗黙的にも明示的にも存在しないので、コンパイラはそれらのインスタンスをコンパイルしません。 cola<float> または cola<string> . をコンパイルするときに、覚えておいてください。 cola.cpp このため、コンパイラはどのインスタンス化が必要なのか見当もつきません。 あらゆる この問題が起きないようにするためです。( cola<int> , cola<char> , cola<ostream> , cola< cola<int> > などなど...)

という2つの答えがあります。

  • コンパイラに cola.cpp のように、特定のテンプレートクラスが必要となるため、強制的にコンパイルされます。 cola<float>cola<string> .
  • メンバ関数の実装をインクルードされるヘッダファイルに記述する あらゆる 他の「翻訳ユニット」(たとえば main.cpp がテンプレートクラスを使用します。

回答1:テンプレートの明示的なインスタンス化、およびそのメンバー定義

において 終了 cola.cpp のように、関連するすべてのテンプレートを明示的にインスタンス化する行を追加する必要があります。

template class cola<float>;
template class cola<string>;

の末尾に次の2行を追加します。 nodo_colaypila.cpp :

template class nodo_colaypila<float>;
template class nodo_colaypila<std :: string>;

これによって、コンパイラがコンパイルする際に cola.cpp のすべてのコードを明示的にコンパイルします。 cola<float>cola<string> クラスがあります。同様に nodo_colaypila.cpp の実装が含まれています。 nodo_colaypila<...> クラスがあります。

このアプローチでは、実装のすべてを1つの .cpp ファイル (つまり 1 つの翻訳ユニット) を作成し、明示的なインスタンス化はすべての関数の定義の後 (つまりファイルの最後) に配置するようにします。

回答2:コードを関連するヘッダーファイルにコピーする

一般的な回答は、実装ファイルからすべてのコードを移動することです。 cola.cppnodo_colaypila.cppcola.hnodo_colaypila.h . 長い目で見れば、この方がより柔軟で、余計なインスタンス化(たとえば cola<char> を使用することで、より多くの作業を行うことができます。しかし、同じ関数が各翻訳ユニットで何度もコンパイルされることになりかねません。リンカは重複する実装を正しく無視するので、これは大きな問題ではありません。しかし、コンパイルが少し遅くなるかもしれません。

概要

STLや多くの人が書くコードで使われているデフォルトの答えは、すべての実装をヘッダーファイルに置くことです。しかし、よりプライベートなプロジェクトでは、どのテンプレート・クラスがインスタンス化されるかについて、より多くの知識と制御を持つことになります。実際、この「バグ」は機能として捉えられるかもしれません。なぜなら、あなたのコードのユーザが、あなたがテストも計画もしていないインスタンス化を誤って使ってしまうことを防げるからです("私はこれが cola<float>cola<string> もし、他のものを使いたい場合は、まず私に言ってください。)

最後に、ご質問のコードには、他に3つの細かい誤字があります。

  • が抜けています。 #endif nodo_colaypila.h の末尾にある
  • cola.hの中で nodo_colaypila<T>* ult, pri;nodo_colaypila<T> *ult, *pri; - はどちらもポインタです。
  • nodo_colaypila.cpp: デフォルトのパラメータはヘッダファイルにあるはずです nodo_colaypila.h この実装ファイルではありません。