我有一些模板代码,我希望将其存储在 CPP 文件中,而不是内联在标题中。我知道只要您知道将使用哪些模板类型就可以做到这一点。例如:
.h 文件
class foo
{
public:
template <typename T>
void do(const T& t);
};
.cpp 文件
template <typename T>
void foo::do(const T& t)
{
// Do something with t
}
template void foo::do<int>(const int&);
template void foo::do<std::string>(const std::string&);
请注意最后两行 - foo::do 模板函数仅与 ints 和 std::strings 一起使用,因此这些定义意味着应用程序将链接。
我的问题是 - 这是一个讨厌的 hack 还是可以与其他编译器/链接器一起使用?我目前仅将此代码与 VS2008 一起使用,但希望移植到其他环境。
do
作为标识符:p
template class foo<int>;template class foo<std::string>;
吗?
您描述的问题可以通过在标题中定义模板或通过您上面描述的方法来解决。
我建议阅读 C++ FAQ Lite 中的以下几点:
为什么我不能将模板类的定义与其声明分开并将其放在 .cpp 文件中?
如何避免模板函数出现链接器错误?
C++ 关键字导出如何帮助解决模板链接器错误?
他们详细介绍了这些(和其他)模板问题。
对于此页面上的其他人想知道显式模板专业化(或至少在 VS2008 中)的正确语法是什么(就像我一样),它的以下...
在您的 .h 文件中...
template<typename T>
class foo
{
public:
void bar(const T &t);
};
在你的 .cpp 文件中
template <class T>
void foo<T>::bar(const T &t)
{ }
// Explicit template instantiation
template class foo<int>;
这段代码格式正确。您只需要注意模板的定义在实例化时是可见的。引用标准,§ 14.7.2.4:
类模板的非导出函数模板、非导出成员函数模板或非导出成员函数或静态数据成员的定义应出现在显式实例化它的每个翻译单元中。
a.cpp
(定义函数a() {}
)和b.cpp
(定义函数b() { a() }
),那么这将成功链接。如果我是对的,那么上面的引用似乎不适用于典型案例......我在某个地方出错了吗?
inline
函数
inline
。原因是如果没有标准化的 C++ ABI,很难/不可能定义否则会产生的影响。
您的示例是正确的,但不是很便携。还有一种更简洁的语法可以使用(正如@namespace-sid 等指出的那样)。
但是,假设模板类是某个要共享的库的一部分......
是否应该编译其他版本的模板类?
库维护者是否应该预测类的所有可能的模板化使用?
另一种方法
添加第三个文件,即源代码中的模板实现/实例化文件。
lib/foo.hpp
在/来自图书馆
#pragma once
template <typename T>
class foo
{
public:
void bar(const T&);
};
lib/foo.cpp
直接编译这个文件只会浪费编译时间
// Include guard here, just in case
#pragma once
#include "foo.hpp"
template <typename T>
void foo::bar(const T& arg)
{
// Do something with `arg`
}
foo.MyType.cpp
使用库,foo<MyType>
的显式模板实例化
// Consider adding "anti-guard" to make sure it's not included in other translation units
#if __INCLUDE_LEVEL__
#error "Don't include this file"
#endif
// Yes, we include the .cpp file
#include <lib/foo.cpp>
#include "MyType.hpp"
template class foo<MyType>;
当然,您可以在第三个文件中有多个实现。或者,您可能需要多个实现文件,例如,您想要使用的每种类型(或一组类型)都有一个。
这种设置应该会减少编译时间,尤其是对于大量使用的复杂模板代码,因为您不会在每个翻译单元中重新编译相同的头文件。它还可以通过编译器和构建脚本更好地检测哪些代码需要重新编译,从而减少增量构建负担。
使用示例
foo.MyType.hpp
需要了解 foo<MyType>
的公共界面而不是 .cpp
来源
#pragma once
#include <lib/foo.hpp>
#include "MyType.hpp"
// Declare `temp`. Doesn't need to include `foo.cpp`
extern foo<MyType> temp;
examples.cpp
可以引用本地声明,但也不会重新编译foo<MyType>
#include "foo.MyType.hpp"
MyType instance;
// Define `temp`. Doesn't need to include `foo.cpp`
foo<MyType> temp;
void example_1() {
// Use `temp`
temp.bar(instance);
}
void example_2() {
// Function local instance
foo<MyType> temp2;
// Use templated library function
temp2.bar(instance);
}
error.cpp
适用于纯标题模板但此处不适用的示例
#include <lib/foo.hpp>
// Causes compilation errors at link time since we never had the explicit instantiation:
// template class foo<int>;
// GCC linker gives an error: "undefined reference to `foo<int>::bar()'"
foo<int> nonExplicitlyInstantiatedTemplate;
void linkerError()
{
nonExplicitlyInstantiatedTemplate.bar();
}
请注意,大多数编译器/linter/代码助手不会将此检测为错误,因为根据 C++ 标准没有错误。但是,当您将此翻译单元链接到完整的可执行文件时,链接器将找不到 foo<int>
的已定义版本。
如果没记错的话,我最初是从 SO 那里得到这个想法的。但是当我写下这个答案时,我一辈子都找不到原来的 SOA。今天,我想我找到了:https://stackoverflow.com/a/495056/4612476
foo-impl.cpp
中)和声明(在 foo.h
中)的实现细节(又名 foo.cpp
中的定义)的分离。我不喜欢大多数 C++ 模板完全在头文件中定义。这与您使用的每个类/命名空间/任何分组的 c[pp]/h
对的 C/C++ 标准背道而驰。人们似乎仍然使用单片头文件,仅仅是因为这种替代方案没有被广泛使用或为人所知。
h/cpp
对时,它仍然有效,尽管我必须将原始实例列表包含在包含保护中,但我仍然可以正常编译 foo.cpp
。不过,我对 C++ 还是很陌生,很想知道这种混合使用是否有任何额外的警告。
foo.cpp
和 foo-impl.cpp
解耦。不要在 foo-impl.cpp
文件中#include "foo.cpp"
;相反,将声明 extern template class foo<int>;
添加到 foo.cpp
以防止编译器在编译 foo.cpp
时实例化模板。确保构建系统构建两个 .cpp
文件并将两个目标文件都传递给链接器。这有很多好处: a) 在 foo.cpp
中很清楚没有实例化; b) 对 foo.cpp 的更改不需要重新编译 foo-impl.cpp。
foo.cpp
重命名为 foo_impl.h
并将 foo-impl.cpp
重命名为 foo.cpp
。我还会为从 foo.cpp
到 foo.h
的实例添加 typedef,同样是 using foo_int = foo<int>;
。诀窍是为用户提供两个标题接口供选择。当用户需要预定义的实例化时,他包括 foo.h
,当用户需要一些乱序的东西时,他包括 foo_impl.h
。
lib/foo.cpp
不应该是 lib/foo.inl
,以便像 cmake 这样的项目生成工具知道它不应该直接编译吗?
这应该可以在支持模板的任何地方正常工作。显式模板实例化是 C++ 标准的一部分。
这是定义模板函数的标准方法。我认为我阅读了三种定义模板的方法。或者可能是 4。每个都有优点和缺点。
在类定义中定义。我根本不喜欢这样,因为我认为类定义仅供参考,应该易于阅读。然而,在类中定义模板比在外面定义模板要简单得多。并不是所有的模板声明都处于相同的复杂程度。此方法还使模板成为真正的模板。在同一个标题中定义模板,但在类之外。这是我大多数时候首选的方式。它使您的类定义保持整洁,模板仍然是真正的模板。然而,它需要完整的模板命名,这可能很棘手。此外,您的代码可供所有人使用。但是,如果您需要内联代码,这是唯一的方法。您还可以通过在类定义的末尾创建一个 .INL 文件来完成此操作。将 header.h 和 implementation.CPP 包含到您的 main.CPP 中。我认为这就是它的完成方式。您不必准备任何预实例化,它的行为就像一个真正的模板。我的问题是它不自然。我们通常不包含并期望包含源文件。我猜由于您包含了源文件,因此可以内联模板函数。最后一种方法,也就是发布的方法,是在源文件中定义模板,就像第 3 条一样;但是我们不包含源文件,而是将模板预实例化为我们需要的模板。我对这种方法没有任何问题,有时它会派上用场。我们有一个大代码,它不能从内联中受益,所以只需将它放在 CPP 文件中。如果我们知道常见的实例化并且我们可以预定义它们。这使我们免于将基本相同的东西写 5 次、10 次。这种方法的好处是保持我们的代码专有。但我不建议在 CPP 文件中放置微小的、经常使用的函数。因为这会降低库的性能。
请注意,我不知道 obj 文件臃肿的后果。
这绝对不是一个讨厌的 hack,但请注意,对于要与给定模板一起使用的每个类/类型,您都必须这样做(显式模板专业化)。如果有许多类型请求模板实例化,您的 .cpp 文件中可能会有很多行。为了解决这个问题,您可以在您使用的每个项目中拥有一个 TemplateClassInst.cpp,这样您就可以更好地控制要实例化的类型。显然,这个解决方案不会是完美的(又名银弹),因为您最终可能会破坏 ODR :)。
举个例子,假设出于某种原因你想要一个模板类:
//test_template.h:
#pragma once
#include <cstdio>
template <class T>
class DemoT
{
public:
void test()
{
printf("ok\n");
}
};
template <>
void DemoT<int>::test()
{
printf("int test (int)\n");
}
template <>
void DemoT<bool>::test()
{
printf("int test (bool)\n");
}
如果您使用 Visual Studio 编译此代码 - 它可以开箱即用。 gcc 将产生链接器错误(如果从多个 .cpp 文件中使用相同的头文件):
error : multiple definition of `DemoT<int>::test()'; your.o: .../test_template.h:16: first defined here
可以将实现移动到 .cpp 文件,但是您需要像这样声明类 -
//test_template.h:
#pragma once
#include <cstdio>
template <class T>
class DemoT
{
public:
void test()
{
printf("ok\n");
}
};
template <>
void DemoT<int>::test();
template <>
void DemoT<bool>::test();
// Instantiate parametrized template classes, implementation resides on .cpp side.
template class DemoT<bool>;
template class DemoT<int>;
然后 .cpp 将如下所示:
//test_template.cpp:
#include "test_template.h"
template <>
void DemoT<int>::test()
{
printf("int test (int)\n");
}
template <>
void DemoT<bool>::test()
{
printf("int test (bool)\n");
}
头文件中没有最后两行 - gcc 可以正常工作,但 Visual Studio 会产生错误:
error LNK2019: unresolved external symbol "public: void __cdecl DemoT<int>::test(void)" (?test@?$DemoT@H@@QEAAXXZ) referenced in function
如果您想通过 .dll 导出公开函数,模板类语法是可选的,但这仅适用于 Windows 平台 - 因此 test_template.h 可能如下所示:
//test_template.h:
#pragma once
#include <cstdio>
template <class T>
class DemoT
{
public:
void test()
{
printf("ok\n");
}
};
#ifdef _WIN32
#define DLL_EXPORT __declspec(dllexport)
#else
#define DLL_EXPORT
#endif
template <>
void DLL_EXPORT DemoT<int>::test();
template <>
void DLL_EXPORT DemoT<bool>::test();
使用上一个示例中的 .cpp 文件。
然而,这让链接器更加头疼,因此如果您不导出 .dll 函数,建议使用前面的示例。
在最新的标准中,有一个关键字 (export
) 可以帮助缓解这个问题,但是除了 Comeau,我所知道的任何编译器都没有实现它。
有关此内容,请参阅 FAQ-lite。
以上都不适合我,所以这就是你解决它的方法,我的班级只有一个模板化的方法..
。H
class Model
{
template <class T>
void build(T* b, uint32_t number);
};
.cpp
#include "Model.h"
template <class T>
void Model::build(T* b, uint32_t number)
{
//implementation
}
void TemporaryFunction()
{
Model m;
m.build<B1>(new B1(),1);
m.build<B2>(new B2(), 1);
m.build<B3>(new B3(), 1);
}
这避免了链接器错误,并且根本不需要调用 TemporaryFunction
是的,这是进行专业化显式实例化的标准方法。正如你所说,你不能用其他类型实例化这个模板。
编辑:根据评论更正。
是时候更新了!创建一个内联(.inl 或任何其他)文件,然后简单地将所有定义复制到其中。请务必在每个函数 (template <typename T, ...>
) 上方添加模板。现在,您不将头文件包含在内联文件中,而是执行相反的操作。在您的类 (#include "file.inl"
) 声明之后包含内联文件。
我真的不知道为什么没有人提到这一点。我认为没有直接的缺点。
#include "file.inl"
后,预处理器会将 file.inl
的内容直接粘贴到标题中。无论您出于何种原因想要避免在标头中执行实现,此解决方案都不能解决该问题。
template
定义所需的。我明白人们为什么要这样做——为了实现与非模板声明/定义的最大一致性,保持接口声明看起来整洁等等——但这并不总是值得麻烦的。这是一个评估双方权衡并选择最差的案例。 ...直到 namespace class
成为事物 :O [请成为事物]
您给出的示例没有任何问题。但我必须说我认为将函数定义存储在 cpp 文件中效率不高。我只理解需要将函数的声明和定义分开。
当与显式类实例化一起使用时,Boost 概念检查库 (BCCL) 可以帮助您在 cpp 文件中生成模板函数代码。
不定期副业成功案例分享