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

Hea*_*iff 136 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>*);

};
Run Code Online (Sandbox Code Playgroud)

然后在"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
}
Run Code Online (Sandbox Code Playgroud)

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

"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
Run Code Online (Sandbox Code Playgroud)

"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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

"的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;
}
Run Code Online (Sandbox Code Playgroud)

但是当我构建时,编译器会在模板类的每个实例中抛出错误:

未定义引用`cola(float):: cola()'...(它实际上是cola'<'float'>':: cola(),但这不允许我这样使用它.)

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

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

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

提前致谢.

Aar*_*aid 351

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

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

了解问题

问题是由于事先main.cpp并且cola.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>;
Run Code Online (Sandbox Code Playgroud)

并在末尾添加以下两行nodo_colaypila.cpp:

template class nodo_colaypila<float>;
template class nodo_colaypila<std :: string>;
Run Code Online (Sandbox Code Playgroud)

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

在这种方法中,您应该确保将所有实现放在一个.cpp文件(即一个转换单元)中,并且在所有函数的定义之后(即在文件末尾)放置显式的瞬时.

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

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

摘要

例如,STL使用的默认答案以及我们任何人编写的大多数代码都是将所有实现放在头文件中.但是在一个更私密的项目中,您将拥有更多的知识和控制权,可以实例化哪些特定的模板类.事实上,这个"错误"可以被看作是一个功能,因为它阻止你的代码的用户意外使用实例你还没有测试或计划("我知道这个工程的cola<float>cola<string>,如果你想使用别的东西,先告诉我,在启用它之前可以验证它是否有效.").

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

  • #endif在nodo_colaypila.h的末尾错过了一个
  • 在cola.h nodo_colaypila<T>* ult, pri;应该nodo_colaypila<T> *ult, *pri;- 都是指针.
  • nodo_colaypila.cpp:默认参数应该在头文件中nodo_colaypila.h,而不是在此实现文件中.

  • 你会认为有人会简化c ++泛型.虽然详细解释+1!:) (3认同)
  • 为了清除这种混淆,这种方法不是黑客,它是完全有效的,并且C++标准支持和批准显式模板即时.但是,它不是最干净的方法(我的观点):它需要你知道所需的所有类型由程序和One需要为你将使用的*all*类型提​​供显式实例化,在一个大项目中,这可能是一个相当大的开销,同时创建最终破坏ODR的可能性. (2认同)

Alo*_*ave 11

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

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

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

定义非导出函数模板,非导出的成员函数模板,或者非导出成员函数或静态数据成员的类模板的应存在于每个翻译单元在其被显式实例.

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

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

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

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

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

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

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


Lia*_*m M 7

这个链接解释了你哪里出错了:

[35.12] 为什么我不能将我的模板类的定义与其声明分开并将它放在一个 .cpp 文件中?

将构造函数、析构函数方法等的定义放在头文件中,这将解决问题。

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

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

但是,这需要您预测模板的使用方式,并且作为一般解决方案,这是违反直觉的。它确实解决了极端情况,尽管您开发了一个供某些内部机制使用的模板,并且您想监管它的使用方式。