c++ - 使用头文件和源文件的模板化类

标签 c++ oop templates include header-files

经过大量研究后,我明白了为什么模板类不能按传统方式分为头文件和源文件。

然而,这 ( Why can templates only be implemented in the header file? ) 似乎暗示您仍然可以通过在头文件末尾包含实现文件来进行某种伪单独编译过程,如下所示:

// 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
}

解释“一个常见的解决方案是在头文件中编写模板声明,然后在实现文件(例如 .tpp)中实现该类,并将此实现文件包含在头文件的末尾。 "

但是,当我这样做时,我收到多个错误,指出这是对构造函数/方法/函数的重新定义。我已经能够通过在 .cpp 文件上放置 include guards 来防止这种情况,这似乎是一种不好的做法。

我的主要问题是:

  1. 在头文件底部包含实现的正确方法是什么?

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

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

下面是我自己的简单 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放入你的头文件中,包括实现#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?

在这里,您提出了一个大的、依赖于平台的、有点基于观点的话题。正如您所说,从历史上看,包含文件用于防止代码复制。将函数声明( header ,而不是定义)包含到多个翻译单元中就足够了,然后将它们与包含函数的已编译代码的单个拷贝链接。

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

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

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

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

关于c++ - 使用头文件和源文件的模板化类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40671478/

相关文章:

c++ - `int` 假定在 OpenCV 中始终为 32 位?

c++ - C 编译器无法创建可执行文件,OpenSolaris Erlang/OTP ./configure 错误

c++ - 是否有可能有一个继承最终函数但创建相同函数(而不是重写)的派生类?

c# - 无法从另一个类调用接口(interface)方法

c++ - 编译错误,MinGW(g++),kinect for windows API

java - 在 Java 序列化中,为什么 J 表示 long 而 L 表示对象?

java - Java中的子类和父类(super class)

c++ - 没有命名类类型的方法函数指针模板

templates - 游戏框架 1.2.4 : get the results of render() as a String?

c++ - 在给定命名声明的宏内使用变量声明的类型