This question already has answers here:
Why can templates only be implemented in the header file?

(17个答案)


5年前关闭。




我不知道为什么会这样,因为我认为我已经正确声明和定义了所有内容。

我有以下程序,使用模板设计。这是队列的简单实现,具有成员函数“add”,“substract”和“print”。

我已经在“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'':: cola(),但这不能让我像这样使用它。)

等等。总共有17条警告,其中包括程序中要调用的成员函数的警告。

为什么是这样?这些函数和构造函数已定义。我认为编译器可以将模板中的“T”替换为“float”,“string”或其他任何东西;那是使用模板的优势。

我在这里某处读到,出于某种原因,我应该将每个函数的声明放在头文件中。那正确吗?如果是这样,为什么?

提前致谢。

最佳答案

这是C++编程中的常见问题。有两个有效的答案。答案都有优点和缺点,您的选择将取决于上下文。常见的答案是将所有实现都放在头文件中,但是在某些情况下还有另一种方法将是合适的。这是你的选择。

模板中的代码仅仅是编译器已知的“模式”。直到被迫编译器才会编译构造函数cola<float>::cola(...)cola<string>::cola(...)。而且,我们必须确保在整个编译过程中至少一次对构造函数进行编译,否则会出现“ undefined reference ”错误。 (这也适用于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> > ...等)

这两个答案是:

  • cola.cpp的末尾告诉编译器,将需要哪些特定的模板类,从而迫使其编译cola<float>cola<string>
  • 将成员函数的实现放在一个头文件中,该文件将在任何其他“翻译单元”(例如main.cpp)每次使用模板类时都包含在头文件中。

  • 答案1:显式实例化模板及其成员定义

    cola.cpp的末尾,您应该添加显式实例化所有相关模板的行,例如
    template class cola<float>;
    template class cola<string>;
    

    然后在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<...>类的实现。

    在这种方法中,您应确保所有实现都放在一个.cpp文件中(即一个翻译单元),并且显式实例化应放在所有函数的定义之后(即在文件末尾)。

    答案2:将代码复制到相关的头文件中

    常见的答案是将所有代码从实现文件cola.cppnodo_colaypila.cpp移到cola.hnodo_colaypila.h。从长远来看,这更加灵活,因为这意味着您可以使用额外的实例化(例如cola<char>)而无需进行任何工作。但这可能意味着相同的函数会多次编译,每个翻译单元一次。这不是一个大问题,因为链接器将正确地忽略重复的实现。但这可能会减慢编译速度。

    概要

    例如,STL以及我们所有人都会编写的大多数代码中使用的默认答案是将所有实现都放在头文件中。但是在一个更私有(private)的项目中,您将拥有更多的知识并控制将实例化哪些特定的模板类。实际上,此“错误”可能被视为一项功能,因为它可以阻止代码的用户意外使用未经测试或计划的实例化(“如果您要使用某些东西,“我知道这适用于cola<float>cola<string>否则,请先告诉我,它将在启用前验证它是否可以运行。”)。

    最后,您的问题代码中还有其他三个小错别字:
  • 您在nodo_colaypila.h末尾缺少#endif
  • cola.h中的
  • nodo_colaypila<T>* ult, pri;应该是nodo_colaypila<T> *ult, *pri;-都是指针。
  • nodo_colaypila.cpp:默认参数应在头文件nodo_colaypila.h中,而不在此实现文件中。
  • 关于c++ - “Undefined reference to”模板类的构造函数,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/8752837/

    10-11 23:07