我有一个链接器错误我已经简化为一个简单的例子。 构建输出是:
debug / main.o:在函数
main':
log& 登录::运营商LT;< (焦 const(&)[6])'
C:\Users\Dani\Documents\Projects\Test1/main.cpp:5: undefined reference to
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;
}
答案 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个解决方案:
2有不同的用途。
(1)是经典的方式。它更容易,因为您不会将用户限制为类型的子集。但是它确实意味着用户依赖于实现(更改它,重新编译,并且您需要在标头中提取依赖项)
(2)使用较少。为了完全符合标准,它需要:
template <> void foo<int>();
)中声明了特化,以便让编译器知道它存在主要优点是,与经典功能一样,您将客户端与实现隔离开来。
gcc
相当宽松,因为你可以放弃声明,它应该有效。
我还应该注意,可以使用不同的实现两次定义方法。这当然是一个错误,因为它直接违反了ODR:One Definition Rule。然而,大多数链接器不报告它,因为每个对象有一个实现是很常见的,他们只选择第一个并假设其他的将是等效的(这是模板的特殊规则)。因此,如果您确实想要使用显式实例化,请务必仅定义一次。
答案 1 :(得分:1)
创建模板时,您应该将标题放在标题中,或者在cpp中使用export
关键字。不幸的是,许多(几乎所有)编译器在这种情况下只忽略export
,所以你唯一的选择是将定义放入头文件中。
答案 2 :(得分:1)
您有两个标准选项:
在头文件中定义运算符