“对模板类构造函数的未定义引用

时间:2012-01-06 02:57:53

标签: c++ templates compiler-errors codeblocks

我不知道为什么会发生这种情况,因为我认为我已经正确地声明和定义了所有内容。

我有以下程序,使用模板设计。这是一个简单的队列实现,成员函数为“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()'... (它实际上是可乐'&lt;'float'&gt;':: cola(),但这并不是'让我像这样使用它。)

等等。总共有17个警告,计算程序中调用成员函数的警告。

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

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

提前致谢。

3 个答案:

答案 0 :(得分:326)

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

模板中的代码只是编译器已知的“模式”。在强制执行此操作之前,编译器不会编译构造函数cola<float>::cola(...)cola<string>::cola(...)。并且我们必须确保在整个编译过程中对构造函数至少进行一次编译,否则我们将得到“未定义的引用”错误。 (这也适用于cola<T>的其他方法。)

了解问题

问题是由于main.cppcola.cpp将首先单独编译而引起的。在main.cpp中,编译器将隐式实例化模板类cola<float>cola<string>,因为这些特定实例在main.cpp中使用。坏消息是这些成员函数的实现不在main.cpp中,也不在main.cpp中包含的任何头文件中,因此编译器不能在{{1}中包含这些函数的完整版本}}。编译main.o时,编译器也不会编译这些实例化,因为没有cola.cppcola<float>的隐式或显式实例化。请记住,在编译cola<string>时,编译器不知道需要哪些实例化;我们不能指望它为每个类型编译,以确保永远不会发生这个问题! (cola.cppcola<int>cola<char>cola<ostream> ......等等......)

这两个答案是:

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

答案1:明确地实例化模板及其成员定义

main.cpp end ,您应该添加明确实例化所有相关模板的行,例如

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.cppcola<float>类的所有代码。同样,cola<string>包含nodo_colaypila.cpp类的实现。

在这种方法中,您应确保将所有实现放入一个nodo_colaypila<...>文件(即一个转换单元),并确保在所有函数的定义之后放置显式的即时(即在文件的结尾)。

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

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

摘要

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

最后,您的问题代码中还有另外三个小错别字:

  • 您在nodo_colaypila.h
  • 末尾缺少cola<float> cola.h中的
  • cola<string>应为#endif - 两者都是指针。
  • nodo_colaypila.cpp:默认参数应位于头文件nodo_colaypila<T>* ult, pri;中,而不是此实现文件中。

答案 1 :(得分:11)

您必须在头文件中定义函数 您不能将模板函数的定义分离到源文件和声明到头文件中。

当模板以触发其instantation的方式使用时,编译器需要查看该特定模板定义。这就是模板通常在声明它们的头文件中定义的原因。

参考:
C ++ 03标准,§14.7.2.4:

  

未导出的函数模板的定义,非导出的成员函数模板,或类模板的非导出成员函数或静态数据成员应存在于每个翻译单元,在其中明确实例化。

修改
澄清对评论的讨论:
从技术上讲,有三种方法可以解决这个链接问题:

  • 将定义移至.h文件
  • .cpp文件中添加显式实例化。
  • #include使用模板在.cpp文件中定义模板的.cpp文件。

他们每个人都有自己的优点和缺点,

将定义移动到头文件可能会增加代码大小(现代编译器可以避免这种情况),但肯定会增加编译时间。

使用显式实例化方法正在转向传统的宏方法。另一个缺点是有必要知道程序需要哪些模板类型。对于简单的程序,这很容易,但对于复杂的程序,这变得难以预先确定。

虽然同时包含cpp文件令人困惑,但同时存在上述两种方法的问题。

我发现第一种方法最容易遵循和实施,因此主张使用它。

答案 2 :(得分:4)

此链接说明了您出错的地方:

[35.12] Why can't I separate the definition of my templates class from its declaration and put it inside a .cpp file?

将构造函数的定义,析构函数方法和诸如此类的东西放在头文件中,这样可以解决问题。

这提供了另一种解决方案:

How can I avoid linker errors with my template functions?

但是,这需要您预测模板的使用方式,并且作为一般解决方案,这是违反直觉的。虽然你开发了一些内部机制使用的模板,但你想要警告它的使用方式,它确实解决了这个问题。