使用 header 和源文件模板化 类

Templated Classes using header and source file

经过大量研究,我明白了为什么模板 类 不能传统上分为 header 和源文件。

但是,这个 (Why can templates only be implemented in the header file?) 似乎暗示您仍然可以通过在 header 文件末尾包含实现文件来进行某种 pseudo-separate-compilation 过程,如图所示以下:

// Foo.h
template <typename T>
struct Foo
{
    void doSomething(T param);
};

#include "Foo.tpp"

// Foo.tpp
template <typename T>
void Foo<T>::doSomething(T param)
{
    //implementation
}

有说明"A common solution to this is to write the template declaration in a header file, then implement the class in an implementation file (for example .tpp), and include this implementation file at the end of the header."

然而,当我这样做时,我收到多个错误,指出它是 constructor/method/function 的重新定义。我已经能够通过在 .cpp 文件上放置 include guards 来防止这种情况,这似乎是一种不好的做法。

我的主要问题是:

  1. 使用 header 文件底部的实现包含的正确方法是什么?

  2. 我的 .cpp 文件中的包含保护是否会阻止编译器为其他类型创建模板class/function,因为它现在只能包含一次?

  3. 首先使用 header 文件的部分原因不是为了防止每次包含代码时都重新复制代码,以保持较短的编译时间吗?那么模板函数(因为它们必须在 header 中定义)与简单地重载 function/class 的性能影响是什么?应该在什么时候使用?

下面是我自己的简单 Node 结构的代码缩减版:

// Node.hpp
#ifndef NODES_H
#define NODES_H
#include <functional>

namespace nodes
{
    template<class Object>
    struct Node
    {
    public:
        Node(Object value, Node* link = nullptr);
        void append(Node tail);

        Object data;
        Node* next;
    };

    template<class Object> void prepend(Node<Object>*& headptr, Node<Object> newHead);
}

// Forward 'declaration' for hash specialization
namespace std
{
    template <typename Object>
    struct hash<nodes::Node<Object>>;
}

#include "Node.cpp"
#endif

// Node.cpp
// #ifndef NODE_CPP
// #define NODE_CPP

#include "Node.hpp"

template<class Object>
nodes::Node<Object>::Node(Object value, Node* link): data(value), next(link) {}

template<class Object>
void nodes::Node<Object>::append(Node tail) {
    Node* current = this;
    while (current->next != nullptr) {
        current = current->next;
    }
    current->next = &tail;
}

template<class Object>
void nodes::prepend(Node<Object>*& headptr, Node<Object> newHead) {
    Node<Object>* newHeadPtr = &newHead;
    Node<Object>* temporary = newHeadPtr;
    while (temporary->next != nullptr) {
        temporary = temporary->next;
    }
    temporary->next = headptr;
    headptr = newHeadPtr;
}

namespace std
{
    template <typename Object>
    struct hash<nodes::Node<Object>>
    {
        size_t operator()(nodes::Node<Object>& node) const
        {
            return hash<Object>()(node.data);
        }
    };
}
// #endif

What is the proper way to do this with the implementation include at the bottom of the header file?

将 include guards 放入您的 header 文件中,包括实施 #include 指令:

#ifndef __FOO_H
#define __FOO_H
// Foo.h
template <typename T>
struct Foo
{
    void doSomething(T param);
};

#include "Foo.tpp"

#endif

您也可以将守卫添加到 Foo.tpp,但在您发布的情况下,它没有多大意义。

Would the include guards on my .cpp file prevent the compiler from creating the templates class/function for other types since it can now only be included once?

通常你根本不需要在 *.cpp 文件中包含守卫,因为你不会在任何地方包含它们。只有那些包含在多个翻译单元中的文件才需要包含守卫。当然,这些守卫不会阻止实例化其他类型的模板,因为这是设计模板的目的。

Isn't part of the reason for using header files in the first place is to prevent code from being recopied every time it is included, to keep a short compilation time? So what is the performance effect of templated functions (since they have to be defined in header) versus simply overloading a function/class? When should each be used?

这里你提出了一个大的,platform-dependent 和一点 opinion-based 的话题。正如您所说,从历史上看,包含文件用于防止代码复制。将函数声明(headers,而不是定义)包含到多个翻译单元中就足够了,然后 link 它们与包含函数的编译代码的单个副本。

模板编译比 non-template 函数慢得多,因此实现模板导出(单独 header/implementation 模板编译)不值得节省编译时间。

有关模板性能的一些讨论和好的答案,请查看这些问题:

简而言之,有时模板允许您在编译时而不是 运行 时做出一些决定,从而使代码更快。无论如何,确定代码是否变得更快的唯一正确方法是在 real-world 环境中进行 运行 性能测试。

最后,模板更多的是关于设计,而不是性能。它们允许您显着减少代码重复并符合 DRY 原则。一个平庸的例子是像 std::max. A more interesting example is Boost.Spirit 这样的函数,它使用模板完全在编译时构建解析器。