提问人:Rob 提问时间:9/22/2008 最后编辑:Leon TimmermansRob 更新时间:9/21/2022 访问量:554014
将 C++ 模板函数定义存储在 .CPP 文件
Storing C++ template function definitions in a .CPP file
问:
我有一些模板代码,我希望将其存储在 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::d o 模板函数仅与 ints 和 std::strings 一起使用,因此这些定义意味着应用程序将链接。
我的问题是 - 这是一个令人讨厌的黑客攻击,还是可以与其他编译器/链接器一起使用?我目前只在 VS2008 中使用此代码,但希望移植到其他环境。
答:
是的,这是执行 specializiation 显式实例化的标准方法。如您所说,您不能使用其他类型实例化此模板。
编辑:根据评论更正。
评论
您描述的问题可以通过在标题中定义模板或通过上面描述的方法来解决。
我建议阅读C++ FAQ Lite中的以下几点:
他们详细介绍了这些(和其他)模板问题。
评论
这应该可以在支持模板的任何地方正常工作。显式模板实例化是 C++ 标准的一部分。
在最新的标准中,有一个关键字 () 可以帮助缓解这个问题,但除了 Comeau 之外,它没有在我所知道的任何编译器中实现。export
有关此内容,请参阅FAQ-lite。
评论
此代码格式正确。您只需要注意模板的定义在实例化时是可见的。引用该标准,§ 14.7.2.4:
类模板的非导出函数模板、非导出成员函数模板或类模板的非导出成员函数或静态数据成员的定义应存在于显式实例化的每个转换单元中。
评论
a.cpp
a() {}
b.cpp
b() { a() }
inline
inline
你给出的例子没有错。但我必须说,我相信将函数定义存储在 cpp 文件中是无效的。我只理解需要将函数的声明和定义分开。
当与显式类实例化一起使用时,Boost 概念检查库 (BCCL) 可以帮助您在 cpp 文件中生成模板函数代码。
评论
这绝对不是一个令人讨厌的黑客,但请注意,您必须为要与给定模板一起使用的每个类/类型执行此操作(显式模板专用化)。如果有许多类型请求模板实例化,则 .cpp 文件中可能有很多行。要解决此问题,您可以在使用的每个项目中都有一个 TemplateClassInst.cpp,以便更好地控制将实例化的类型。显然,这个解决方案并不完美(又名银弹),因为您最终可能会破坏 ODR :)。
评论
对于此页面上的其他人想知道显式模板专用化(或至少在 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>;
评论
是时候更新了!创建一个内联(.inl,或任何其他)文件,只需将所有定义复制到其中即可。请务必在每个函数 () 上方添加模板。现在,您没有将头文件包含在内联文件中,而是反其道而行之。在类声明 () 后包含内联文件。template <typename T, ...>
#include "file.inl"
我真的不知道为什么没有人提到这一点。我看不出有什么直接的缺点。
评论
#include "file.inl"
file.inl
命名空间类
成为事物:O[请成为一件事template
]
您的示例是正确的,但不是很便携。 还可以使用稍微干净的语法(正如 @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`
}
傅。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>;
根据需要组织实施:
- 所有实现都集中在一个文件中
- 多个实现文件,每种类型一个
- 每组类型的实现文件
为什么??
此设置应减少编译时间,尤其是对于大量使用的复杂模板化代码,因为您不会在每个文件中重新编译相同的头文件 翻译单位。 它还能够通过编译器和构建脚本更好地检测哪些代码需要重新编译,从而减少增量构建负担。
使用示例
傅。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;
例子.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);
}
错误 .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();
}
注意:大多数编译器/linters/代码助手不会将此检测为错误,因为根据 C++ 标准没有错误。
但是,当您将此转换单元链接到完整的可执行文件时,链接器将找不到 foo<int>
的定义版本。
替代方法来自:https://stackoverflow.com/a/495056/4612476
评论
foo.cpp
foo-impl.cpp
foo.h
c[pp]/h
h/cpp
foo.cpp
foo.cpp
foo-impl.cpp
#include "foo.cpp"
foo-impl.cpp
extern template class foo<int>;
foo.cpp
foo.cpp
.cpp
foo.cpp
foo.cpp
foo_impl.h
foo-impl.cpp
foo.cpp
foo.cpp
foo.h
using foo_int = foo<int>;
foo.h
foo_impl.h
lib/foo.cpp
lib/foo.inl
这是定义模板函数的标准方法。我认为我读到三种定义模板的方法。或者可能是 4 个。每个都有优点和缺点。
在类定义中定义。我一点也不喜欢这样,因为我认为类定义仅供参考,应该易于阅读。但是,在课堂上定义模板比在课堂外定义模板要麻烦得多。并非所有模板声明都处于相同的复杂程度。此方法还使模板成为真正的模板。
在同一标头中定义模板,但在类之外。大多数时候,这是我的首选方式。它使您的类定义保持整洁,模板仍然是真正的模板。但是,它需要完整的模板命名,这可能很棘手。此外,您的代码可供所有人使用。但是,如果你需要你的代码是内联的,这是唯一的方法。您也可以通过创建 .类定义末尾的 INL 文件。
包括 header.h 和实现。CPP 进入您的主线。CPP。我认为这就是它的方式。您无需准备任何预实例化,它的行为就像一个真正的模板。我的问题是它不自然。我们通常不包括并期望包含源文件。我想既然您包含了源文件,那么模板函数就可以内联了。
最后一种方法是发布的方式,是在源文件中定义模板,就像数字 3 一样;但是,我们没有包含源文件,而是将模板预先实例化为我们需要的模板。我对这种方法没有问题,有时它会派上用场。我们有一个大代码,它不能从内联中受益,所以只需将其放在 CPP 文件中即可。如果我们知道常见的实例化,我们可以预定义它们。这使我们免于写 5 到 10 次基本上相同的东西。这种方法的好处是保持我们的代码专有性。但我不建议将微小的、经常使用的函数放在 CPP 文件中。因为这会降低库的性能。
请注意,我不知道臃肿的 obj 文件的后果。
让我们举一个例子,假设出于某种原因,你想要一个模板类:
//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 函数,建议使用前面的示例。
评论
以上都不适合我,所以这是你如何解决的,我的班级只有 1 个模板化的方法。
.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
评论
上一个:什么是非演绎上下文?
评论
do