关于c ++:“模板类构造函数的未定义引用”

“Undefined reference to” template class constructor

本问题已经有最佳答案,请猛点这里访问。

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

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

我已经在精细的"nodo-colaypila.h"中定义了队列的节点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#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"中实现

1
2
3
4
5
6
7
8
#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
}

然后,队列模板类及其函数的定义和声明:

"可乐。H":

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#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

"可乐·CPP":

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#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;
    }
}

然后,我有一个程序来测试这些功能,如下所示:

"主CPP"

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#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()"的引用。(实际上是可乐"<‘float’>"::cola(),但这不允许我这样使用。)

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

为什么会这样?定义了这些函数和构造函数。我认为编译器可以用"float"、"string"或其他东西替换模板中的"t";这是使用模板的优点。

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

事先谢谢。


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

模板中的代码只是编译器已知的"模式"。编译器不会编译构造函数cola::cola(...)cola::cola(...),除非强制编译。我们必须确保在整个编译过程中,至少对构造函数进行一次编译,否则会出现"未定义引用"错误。(这也适用于cola的其他方法。)好的。了解问题所在

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

这两个答案是:好的。

  • 告诉编译器,在cola.cpp的末尾,需要哪些特定的模板类,强制它编译colacola
  • 将成员函数的实现放在一个头文件中,该头文件将在每次任何其他"翻译单元"(如main.cpp使用模板类时包含该头文件)。

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

cola.cpp的末尾,应该添加显式实例化所有相关模板的行,例如好的。

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

nodo_colaypila.cpp的末尾加上以下两行:好的。

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

这将确保在编译器编译cola.cpp时,它将显式编译colacola类的所有代码。同样,nodo_colaypila.cpp包含nodo_colaypila<...>类的实现。好的。

在这种方法中,您应该确保所有实现都放在一个.cpp文件(即一个翻译单元)中,并且显式实例化放在所有函数定义之后(即文件末尾)。好的。答案2:将代码复制到相关的头文件中

常见的答案是将所有代码从实现文件cola.cppnodo_colaypila.cpp移到cola.hnodo_colaypila.h中。从长远来看,这更为灵活,因为这意味着您可以在不做任何工作的情况下使用额外的实例化(例如cola)。但这可能意味着相同的函数被多次编译,在每个翻译单元中只编译一次。这不是一个大问题,因为链接器将正确地忽略重复的实现。但它可能会稍微放慢编译速度。好的。总结

例如,STL和我们任何人将要编写的大多数代码中使用的默认答案是将所有实现放在头文件中。但是在一个更私有的项目中,您将拥有更多的知识和控制,可以实例化特定的模板类。事实上,这个"bug"可能被视为一个特性,因为它可以防止代码的用户意外地使用您没有测试或计划使用的实例化("我知道这适用于colacola,如果您想使用其他东西,请先告诉我,并将在启用它之前验证它的工作情况。")。好的。

最后,在您的问题中,代码中还有三个小的拼写错误:好的。

  • 你在nodo-colaypila.h的末尾缺少一个#endif
  • 在cola.h中,nodo_colaypila* ult, pri;应该是nodo_colaypila *ult, *pri;,两者都是指针。
  • nodo-colaypila.cpp:默认参数应该在头文件nodo_colaypila.h中,而不是在这个实现文件中。

好啊。


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

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

参考文献:C++03标准,〈7.7.2.4:

The definition of a non-exported function template, a non-exported member function template, or a non-exported member function or static data member of a class template shall be present in every translation unit in which it is explicitly instantiated.

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

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

他们每个人都有各自的利弊,

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

使用显式实例化方法正在回到传统的类似宏的方法,另一个缺点是需要知道程序需要哪些模板类型。对于一个简单的程序来说,这很容易,但是对于复杂的程序来说,这很难提前确定。

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

我发现第一个方法是最容易遵循和实现的,因此advocte使用它。


此链接解释了哪里出错:

[35.12]为什么我不能将模板类的定义从声明中分离出来,并将其放在.cpp文件中?

将构造函数、析构函数方法和其他内容的定义放在头文件中,这样可以纠正问题。

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

如何避免模板函数出现链接器错误?

然而,这要求您预测模板将如何使用,并且作为一个通用的解决方案,这是违反直觉的。它确实解决了一个角点问题,尽管在这里,您开发了一个供某些内部机制使用的模板,并且您希望对模板的使用方式进行管理。