模板操作员链接器错误

时间:2010-06-09 15:26:11

标签: c++ templates linker

我有一个链接器错误我已经简化为一个简单的例子。 构建输出是:

  

debug / main.o:在函数main':
C:\Users\Dani\Documents\Projects\Test1/main.cpp:5: undefined reference to
log&   登录::运营商LT;< (焦   const(&)[6])'
  collect2:ld返回   1退出状态

看起来链接器忽略了log.cpp中的定义 我也不能把这个定义放在log.h中,因为我很多次都包含了这个文件,并抱怨重新定义。

main.cpp中:

#include "log.h"

int main()
{
    log() << "hello";
    return 0;
}

log.h:

#ifndef LOG_H
#define LOG_H

class log
{
public:
    log();
    template<typename T>
    log &operator <<(T &t);
};

#endif // LOG_H

log.cpp:

#include "log.h"
#include <iostream>

log::log()
{
}

template<typename T>
log &log::operator <<(T &t)
{
    std::cout << t << std::endl;
    return *this;
}

3 个答案:

答案 0 :(得分:4)

我想这是你第一次使用模板,所以我会尝试做教学。

您可以将模板视为某种类型感知宏。这种类型意识当然不容忽视,因为它免费提供类型安全。这确实意味着模板函数或类不是函数或类:它们是用于生成函数或类的模型。

例如:

template <class T>
void foo(T t) { std::cout << t << "\n"; }

这是一个模板函数,它允许我定义一次并将其应用于许多不同的类型。

int i;
foo(i); // [1]

这会导致template的实例化。基本上,它意味着根据模型创建函数,但用T替换所有int的出现。

double d;
foo(d);    // Another instantiation, this time with `T` replaced by `double`
foo(d);    // foo<double>() already exists, it's reused

现在,这种模型的想法非常重要。如果头文件中不存在模型的定义,则编译器不知道如何定义方法。

所以,你有2个解决方案:

  1. 在标题中定义
  2. 明确地实例化
  3. 2有不同的用途。

    (1)是经典的方式。它更容易,因为您不会将用户限制为类型的子集。但是它确实意味着用户依赖于实现(更改它,重新编译,并且您需要在标头中提取依赖项)

    (2)使用较少。为了完全符合标准,它需要:

    • 您在标题(template <> void foo<int>();)中声明了特化,以便让编译器知道它存在
    • 您在其中一个链接的翻译单元中完全定义了

    主要优点是,与经典功能一样,您将客户端与实现隔离开来。

    gcc相当宽松,因为你可以放弃声明,它应该有效。

    我还应该注意,可以使用不同的实现两次定义方法。这当然是一个错误,因为它直接违反了ODR:One Definition Rule。然而,大多数链接器不报告它,因为每个对象有一个实现是很常见的,他们只选择第一个并假设其他的将是等效的(这是模板的特殊规则)。因此,如果您确实想要使用显式实例化,请务必仅定义一次。

答案 1 :(得分:1)

创建模板时,您应该将标题放在标题中,或者在cpp中使用export关键字。不幸的是,许多(几乎所有)编译器在这种情况下只忽略export,所以你唯一的选择是将定义放入头文件中。

答案 2 :(得分:1)

您有两个标准选项:

  1. 在头文件中定义运算符

  2. Explicitly instantiate the template