使用头文件和源文件的模板化类
Templated Classes using header and source 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守卫来防止这种情况,这似乎是一种糟糕的做法。
我的主要问题是:
什么是正确的方法来做到这一点与实现包括在头文件的底部?
我的。cpp文件上的包含保护是否会阻止编译器为其他类型创建模板类/函数,因为它现在只能包含一次?
首先使用头文件的部分原因是为了防止每次包含代码时都被复制,以保持较短的编译时间?那么,模板化函数(因为它们必须在头文件中定义)与简单地重载函数/类的性能影响是什么?什么时候应该使用它们?
下面是我自己的简单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
在头文件的底部包含实现的正确方法是什么?
将include警卫放入头文件,包括实现#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
文件中包含守卫,因为你不会在任何地方包含它们。只需要在包含在多个翻译单元中的那些文件中使用Include警卫。当然,这些保护不会阻止为其他类型实例化模板,因为这是模板设计的目的。
首先使用头文件的部分原因不是为了防止每次包含代码时都被复制,以保持较短的编译时间吗?那么,模板化函数(因为它们必须在头文件中定义)与简单地重载函数/类的性能影响是什么?什么时候应该使用它们?
你在这里提出了一个大的、平台相关的、有点基于观点的话题。历史上,include文件用于防止代码复制,正如您所说的。将函数声明(头文件,而不是定义)包含到多个翻译单元中,然后将它们与所包含函数的编译代码的单个副本链接起来就足够了。
模板的编译速度比非模板函数慢得多,所以实现模板导出(单独的模板头/实现编译)不值得节省编译时间。
关于模板性能的一些讨论和好的答案,请检查这些问题:
- 模板元编程比等效的C代码更快吗?
- c++模板的性能? c++模板会使程序变慢吗?
简而言之,有时模板允许您在编译时而不是运行时做出一些决定,从而使代码更快。无论如何,确定代码是否变得更快的唯一正确方法是在实际环境中运行性能测试。
最后,模板更多的是关于设计,而不是性能。它们允许您显著减少代码重复并符合DRY原则。一个普通的例子是std::max这样的函数。一个更有趣的例子是Boost。
- 为测试目标创建具有不同源文件夹的文件
- 生成一个生成文件,该生成文件使用Automake在一个步骤中编译和链接所有源文件
- 生成文件:动态源文件名和对象目录
- 无法使用 CMake 从其他文件夹添加源文件
- CMake 源文件找不到头文件
- 给定一个源文件,有没有办法要求 gcc 返回仅直接包含的头文件的列表?
- UE4 - Visual Studio在我从编辑器添加新的c ++文件后无法打开任何源文件 - UBT_COMPILED
- cpp 在主源文件中包括.cpp文件导致错误"duplicate symbol"
- 如何将 .ui 完全转换为 C++ 头文件和源文件
- 在生成文件中添加源文件并更新依赖项
- CMake 在源文件附近找不到头文件
- GNU 在看到其他同名文件时重用源文件
- #include "date.h" 创建错误 E1696 无法打开源文件"date.h",也无法打开包含文件:没有这样的文件或目录
- DirectX 11 文件无法打开源文件
- 用libclang解析源文件 - 链接问题包括文件
- 获取对源文件中特定函数的所有调用并生成其他文件(使用 C、C++预处理器或脚本)
- 我有一个预处理的 C/C++ 源文件 (cacti.i).如何从这个 .i 文件生成可执行二进制文件,以便我可以像 ./
- 创建 Matlab MEX 函数时,我是否将 mexFunction 放在 c++ 头文件或源文件中
- Linux 可执行文件中列出的源文件
- 使用GCC的C makefile - 从子文件夹列表中生成源文件列表