小编典典

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

all

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

我有以下程序,用模板设计。这是一个队列的简单实现,具有成员函数“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
}

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

“可乐.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

“可乐.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;
    }
}

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

“主.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’<’float’>’::cola(),但这不允许我那样使用它。)

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

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

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


阅读 111

收藏
2022-07-27

共1个答案

小编典典

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

模板中的代码只是编译器已知的“模式”。编译器不会编译构造函数cola<float>::cola(...)cola<string>::cola(...)直到它被迫这样做。而且我们必须确保在整个编译过程中构造函数
至少发生一次这种编译,否则我们将得到“未定义引用”错误。 (这也适用于的其他方法cola<T>。)

理解问题

main.cpp问题是由和cola.cpp将首先单独编译的事实引起的。在main.cpp中,编译器将 隐式
实例化模板类cola<float>cola<string>因为在main.cpp. 坏消息是这些成员函数的实现main.cpp不在.
编译时,编译器也不会编译这些实例化,因为 or
没有隐式或显式实例化。记住,编译的时候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.cppand .
从长远来看,这更灵活,因为这意味着您可以使用额外的实例化(例如)而无需更多工作。但这可能意味着相同的函数被编译多次,每个翻译单元一次。这不是一个大问题,因为链接器会正确地忽略重复的实现。但它可能会稍微减慢编译速度。nodo_colaypila.cpp``cola.h``nodo_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中,而不是在这个实现文件中。
2022-07-27