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

Storing C++ template function definitions in a .CPP file

我有一些模板代码希望存储在cpp文件中,而不是在头中内联。我知道只要您知道将使用哪种模板类型,就可以这样做。例如:

h文件

1
2
3
4
5
6
class foo
{
public:
    template <typename T>
    void do(const T& t);
};

.CPP文件

1
2
3
4
5
6
7
8
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,因此这些定义意味着应用程序将链接。

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


您描述的问题可以通过在标题中定义模板来解决,或者通过您上面描述的方法来解决。

我建议从C++ FAQ Lite中阅读以下几点:

  • 为什么我不能从模板类的声明中分离模板类的定义并将其放入.cpp文件中?
  • 如何避免模板函数出现链接器错误?
  • C++关键字导出如何帮助模板链接器错误?

他们详细介绍了这些(和其他)模板问题。


对于本页上的其他人,如果想知道显式模板专门化(或者至少在VS2008中)的正确语法是什么(和我一样),请参阅以下内容…

在你的.h文件中…

1
2
3
4
5
6
template<typename T>
class foo
{
public:
    void bar(const T &t);
};

在你的.cpp文件中

1
2
3
4
5
6
template <class T>
void foo<T>::bar(const T &t)
{ }

// Explicit template instantiation
template class foo<int>;


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

The definition of a non-exported function template, a non-exported member function template, or a non-exported member function or static data member of a class template shall be present in every translation unit in which it is explicitly instantiated.


这在支持模板的任何地方都可以很好地工作。显式模板实例化是C++标准的一部分。


您的示例是正确的,但不是非常可移植的。还有一个稍微干净一点的语法可以使用(如@namespace sid所指出的)。

假设模板类是要共享的某个库的一部分。是否应编译模板类的其他版本?库维护人员是否应该预测类的所有可能的模板化使用?

另一种方法是对您所拥有的内容进行细微的更改:添加第三个文件,即模板实现/实例化文件。

fo.h文件

1
2
3
4
5
6
7
8
// Standard header file guards omitted

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

文件文件

1
2
3
4
5
6
7
8
// Always include your headers
#include"foo.h"

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

foo-impl.cpp文件

1
2
3
// Yes, we include the .cpp file
#include"foo.cpp"
template class foo<int>;

一个警告是,您需要告诉编译器编译foo-impl.cpp,而不是foo.cpp,因为编译EDOCX1不起任何作用。

当然,您可以在第三个文件中有多个实现,或者对于您想要使用的每种类型有多个实现文件。

当共享模板类以供其他用途时,这使得灵活性大大提高。

这个设置还减少了重用类的编译时间,因为您没有在每个翻译单元中重新编译相同的头文件。


这绝对不是一个令人讨厌的黑客,但要注意的是,对于您想要与给定模板一起使用的每一个类/类型,都必须这样做(显式模板专门化)。在许多类型请求模板实例化的情况下,.cpp文件中可能有很多行。为了解决这个问题,您可以在您使用的每个项目中都有一个templateclassinst.cpp,这样您就可以更好地控制将要实例化的类型。显然,这个解决方案不会是完美的(也就是银弹),因为你可能最终会打破ODR:。


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

请参阅有关此问题的FAQ Lite。


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

编辑:根据注释更正。


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

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

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

  • 在main.cpp中包含header.h和implementation.cpp。我想是这样的。您不必准备任何预实例化,它的行为就像一个真正的模板。我有一个问题,那是不自然的。我们通常不包括并希望包括源文件。我想既然您包含了源文件,模板函数就可以被内联。

  • 最后一个方法,即发布的方法,是在源文件中定义模板,就像数字3一样;但是我们不包括源文件,而是将模板预实例化为我们需要的模板。我对这个方法没有问题,有时它也很有用。我们有一个大代码,它不能从内联中受益,所以只需将它放在一个cpp文件中。如果我们知道常见的实例化,我们可以预先定义它们。这就避免了我们写5,10次基本相同的东西。这种方法的好处是保持我们的代码是专有的。但我不建议在cpp文件中放置小的、经常使用的函数。因为这样会降低库的性能。

  • 注意,我不知道obj文件膨胀的后果。


    该更新了!创建一个内联(.inl,或任何其他)文件,并简单地复制其中的所有定义。一定要在每个函数(template 上添加模板。现在,不要将头文件包含在内联文件中,而是执行相反的操作。在类的声明之后包括内联文件(#include"file.inl")。

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


    你给出的例子没有错。但我必须说,我相信在cpp文件中存储函数定义是不有效的。我只理解分离函数声明和定义的需要。

    当与显式类实例化一起使用时,boost概念检查库(bccl)可以帮助您在cpp文件中生成模板函数代码。