模板运算符链接器错误

Dan*_*ani 4 c++ linker templates

我有一个链接器错误,我已将其简化为一个简单的示例。构建输出是:

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

看起来链接器忽略了 log.cpp 中的定义。
我也无法将定义放入 log.h 中,因为我多次包含该文件,并且它抱怨重新定义。

主要.cpp:

#include "log.h"

int main()
{
    log() << "hello";
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

日志.h:

#ifndef LOG_H
#define LOG_H

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

#endif // LOG_H
Run Code Online (Sandbox Code Playgroud)

日志.cpp:

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

log::log()
{
}

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

Mat*_* M. 5

我想这是您第一次使用模板,所以我会尝试说教。

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

例如:

template <class T>
void foo(T t) { std::cout << t << "\n"; }
Run Code Online (Sandbox Code Playgroud)

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

int i;
foo(i); // [1]
Run Code Online (Sandbox Code Playgroud)

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

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

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

所以,这里有两个解决方案:

  1. 在标题中定义它
  2. 显式实例化它

2者有不同的用途。

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

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

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

主要优点是,与经典函数一样,您可以将客户端与实现隔离。

gcc是相当宽松的,因为你可以放弃声明,它应该可以工作。

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