使用标头和源文件

时间:2016-11-18 07:20:44

标签: 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
}

通过解释&#34;一个常见的解决方案是在头文件中编写模板声明,然后在实现文件中实现该类(例如.tpp),并在结尾处包含此实现文件。标题。&#34;

然而,当我这样做时,我得到多个错误,说明它是构造函数/方法/函数的重新定义。我已经能够通过在.cpp文件上添加包含防护来防止这种情况,这似乎是不好的做法。

我的主要问题是:

  1. 使用头文件底部的实现来执行此操作的正确方法是什么?

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

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

  4. 以下是我自己的简单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
    

1 个答案:

答案 0 :(得分:0)

  

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

将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,但在您发布的情况下,它将没有多大意义。

  

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

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

  

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

在这里,你提出了一个大的,平台相关的和基于意见的主题。从历史上看,正如您所说,包含文件用于防止代码复制。将函数声明(标题,而不是定义)包含到多个翻译单元中,然后将它们与包含函数的已编译代码的单个副本链接起来就足够了。

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

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

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

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