在.CPP文件中存储c++模板函数定义

我有一些模板代码,我宁愿存储在一个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模板函数只用于int和std::string,所以这些定义意味着应用程序将链接。

我的问题是-这是一个讨厌的黑客或这将与其他编译器/链接器工作吗?目前我只在VS2008中使用这段代码,但我想移植到其他环境中。

494651 次浏览

是的,这是执行specializiation显式实例化的标准方法。如您所述,您不能用其他类型实例化此模板。

编辑:根据评论进行更正。

这应该在支持模板的任何地方都能正常工作。显式模板实例化是c++标准的一部分。

在最新的标准中,有一个关键字(export)可以帮助缓解这个问题,但是在我所知道的任何编译器中都没有实现,除了Comeau。

请参阅FAQ-lite

此代码是格式良好的。您只需注意模板的定义在实例化时是可见的。引用标准§14.7.2.4:

非导出函数模板、非导出成员函数模板或类模板的非导出成员函数或静态数据成员的定义应出现在显式实例化的每个转换单元中。

你举的例子没有问题。但是我必须说,我认为在cpp文件中存储函数定义是不有效的。我只理解需要将函数的声明和定义分开。

当与显式类实例化一起使用时,Boost Concept Check Library (BCCL)可以帮助您在cpp文件中生成模板函数代码。

这绝对不是一个令人讨厌的hack,但要注意这样一个事实,即您必须对您想要与给定模板一起使用的每个类/类型都这样做(显式模板专门化)。如果有很多类型请求模板实例化,那么你的.cpp文件中就会有很多行。为了解决这个问题,您可以在您使用的每个项目中使用templateclassint .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")的声明中。

我真的不知道为什么没有人提到这一点。我看不出有什么直接的缺点。

你的例子是正确的,但不是很可移植。 还有一种稍微干净一点的语法可以使用(如@namespace-sid等指出的那样)

然而,假设模板化类是某个库的一部分,该库将被共享…

是否应该编译模板化类的其他版本?

库维护者是否应该预测类的所有可能的模板使用?

另一种方法

在源代码中添加第三个文件,即模板实现/实例化文件。

# eyz1 - # eyz2

#pragma once


template <typename T>
class foo {
public:
void bar(const T&);
};

# eyz1 - # eyz2

// Include guard here, just in case
#pragma once


#include "foo.hpp"


template <typename T>
void foo::bar(const T& arg) {
// Do something with `arg`
}

# eyz2 - # eyz3

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

按照需要组织你的实现:

  • 所有实现都在一个文件中
  • 多个实现文件,每种类型一个
  • 每个类型集的实现文件

为什么? ?

这种设置应该减少编译时间,特别是对于大量使用的复杂模板代码,因为您不需要在每个模板中重新编译相同的头文件 翻译单元。 它还可以更好地检测哪些代码需要由编译器和构建脚本重新编译,减少增量构建负担

用法示例

# eyz3 - # eyz4

#pragma once


#include <lib/foo.hpp>
#include "MyType.hpp"


// Declare `temp`. Doesn't need to include `foo.cpp`
extern foo<MyType> temp;

# eyz2 - # eyz3

#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);
}

# eyz1 - # eyz2

#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>.

的定义版本

替代方法:https://stackoverflow.com/a/495056/4612476

这是定义模板函数的标准方法。我认为有三种定义模板的方法。或者可能是4个。每一种都有利弊。

  1. 在类定义中定义。我一点也不喜欢这样,因为我认为类定义严格来说是为了参考,应该易于阅读。然而,在类中定义模板比在类外定义模板要容易得多。而且并非所有模板声明的复杂度都相同。此方法还使模板成为真正的模板。

  2. 在相同的头文件中定义模板,但是在类之外。这是我大多数时候喜欢的方式。它使你的类定义保持整洁,模板仍然是一个真正的模板。然而,它需要完整的模板命名,这可能很棘手。此外,所有人都可以使用您的代码。但如果你需要你的代码内联,这是唯一的方法。您也可以通过在类定义的末尾创建. inl文件来实现这一点。

  3. 将header.h和implementation.CPP包含到main.CPP中。我想事情就是这样的。你不需要准备任何预实例化,它的行为就像一个真正的模板。我的问题是,它不是自然的。我们通常不包括也不期望包括源文件。我想既然包含了源文件,模板函数就可以内联了。

  4. 最后一个方法,也就是发布的方法,是在源文件中定义模板,就像第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