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

Rob*_*Rob 484 c++ templates

我有一些模板代码,我宁愿存储在CPP文件中而不是标题中的内联.我知道只要您知道将使用哪些模板类型,就可以完成此操作.例如:

.h文件

class foo
{
public:
    template <typename T>
    void do(const T& t);
};
Run Code Online (Sandbox Code Playgroud)

.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&);
Run Code Online (Sandbox Code Playgroud)

注意最后两行--foo :: do模板函数仅用于int和std :: strings,因此这些定义意味着应用程序将链接.

我的问题是 - 这是一个讨厌的黑客还是会与其他编译器/链接器一起使用?我目前只在VS2008上使用此代码,但是想要移植到其他环境.

Aar*_*bbs 216

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

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

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

  • 你能在答案中发布相关部分吗?为什么在SO上允许这样的引用.我不知道在这个链接中要查找什么,因为它已经发生了很大变化. (138认同)
  • 为了补充答案,引用的链接肯定地回答了问题,即可以执行Rob建议并使代码可移植. (33认同)

小智 113

对于本页面上的其他人,想知道显式模板特化(或者至少在VS2008中)的正确语法是什么(就像我一样),它的以下内容......

在你的.h文件中......

template<typename T>
class foo
{
public:
    void bar(const T &t);
};
Run Code Online (Sandbox Code Playgroud)

并在您的.cpp文件中

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

// Explicit template instantiation
template class foo<int>;
Run Code Online (Sandbox Code Playgroud)

  • 你的意思是"明确的CLASS模板专业".在那种情况下,它将覆盖模板类具有的每个函数吗? (12认同)
  • 我对你在这里使用类型名和类完全感到困惑.. (4认同)

Kon*_*lph 22

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

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

  • *非出口*是什么意思? (2认同)
  • @Dan 仅在其编译单元内可见,而不在其外部可见。如果将多个编译单元链接在一起,则可以在它们之间使用导出的符号(并且必须具有单个,或者至少在模板的情况下,具有一致的定义,否则会遇到 UB)。 (2认同)

moo*_*dow 13

这应该适用于支持模板的所有地方.显式模板实例化是C++标准的一部分.


Cam*_*ind 10

你的例子是正确的,但不是很便携.还可以使用稍微清晰的语法(如@ namespace-sid所指出的).

假设模板化类是要共享的某个库的一部分.是否应编译其他版本的模板化类?图书馆维护者是否应该预测该课程的所有可能的模板用途?

另一种方法是对您所拥有的内容略有不同:添加第三个文件,即模板实现/实例化文件.

foo.h文件

// Standard header file guards omitted

template <typename T>
class foo
{
public:
    void bar(const T& t);
};
Run Code Online (Sandbox Code Playgroud)

foo.cpp文件

// Always include your headers
#include "foo.h"

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

foo-impl.cpp文件

// Yes, we include the .cpp file
#include "foo.cpp"
template class foo<int>;
Run Code Online (Sandbox Code Playgroud)

需要注意的是,您需要告诉编译器编译foo-impl.cpp而不是foo.cpp编译,后者什么都不做.

当然,您可以在第三个文件中有多个实现,或者为您要使用的每种类型提供多个实现文件.

当共享模板化类用于其他用途时,这可以提供更大的灵活性.

此设置还减少了重用类的编译时间,因为您不会在每个转换单元中重新编译相同的头文件.

  • 这是解决模板定义问题的一个非常好的方法,它充分利用了两个方面的优点——标头实现和常用类型的实例化。我对此设置所做的唯一更改是将“foo.cpp”重命名为“foo_impl.h”,并将“foo-impl.cpp”重命名为“foo.cpp”。我还会为从 `foo.cpp` 到 `foo.h` 的实例化添加 typedef,同样是 `using foo_int = foo&lt;int&gt;;`。诀窍是为用户提供两个标头接口以供选择。当用户需要预定义的实例化时,他包含“foo.h”,当用户需要乱序的东西时,他包含“foo_impl.h”。 (6认同)
  • 分离实际编译版本(在“foo-impl.cpp”中)和声明(在“foo.h”中)的实现细节(又名“foo.cpp”中的定义)。我不喜欢大多数 C++ 模板完全在头文件中定义。这与每个类/命名空间/您使用的任何分组的“c[pp]/h”对的 C/C++ 标准相反。人们似乎仍然使用整体头文件,仅仅是因为这种替代方案尚未广泛使用或为人所知。 (3认同)
  • 我认为最好将foo.cpp和foo-impl.cpp解耦。不要在foo-impl.cpp文件中使用#include“ foo.cpp”。相反,应在foo.cpp中添加声明“ extern template class foo &lt;int&gt;;”,以防止编译器在编译foo.cpp时实例化模板。确保构建系统生成两个.cpp文件,并将两个目标文件都传递给链接器。这有很多好处:a)在foo.cpp中很明显没有实例化;b)对foo.cpp的更改不需要重新编译foo-impl.cpp。 (3认同)
  • 难道“lib/foo.cpp”不应该是“lib/foo.inl”,这样像cmake这样的项目生成工具就知道它不应该直接编译吗? (2认同)

Red*_*III 5

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


小智 5

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

  1. 在类定义中定义。我根本不喜欢这样,因为我认为类定义仅供参考,并且应该易于阅读。然而,在类中定义模板比在外部定义要简单得多。并不是所有的模板声明都处于同一级别的复杂性。此方法还使模板成为真正的模板。

  2. 在同一个标​​题中定义模板,但在类之外。大多数时候这是我的首选方式。它使您的类定义保持整洁,模板仍然是真正的模板。然而,它需要完整的模板命名,这可能很棘手。此外,您的代码可供所有人使用。但是,如果您需要内联代码,这是唯一的方法。您还可以通过在类定义的末尾创建一个 .INL 文件来完成此操作。

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

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

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


Tar*_*aro 5

让我们举一个例子,假设出于某种原因你想要一个模板类:

//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");
}
Run Code Online (Sandbox Code Playgroud)

如果您使用 Visual Studio 编译此代码 - 它开箱即用。gcc 将产生链接器错误(如果从多个 .cpp 文件使用相同的头文件):

error : multiple definition of `DemoT<int>::test()'; your.o: .../test_template.h:16: first defined here
Run Code Online (Sandbox Code Playgroud)

可以将实现移动到 .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>;
Run Code Online (Sandbox Code Playgroud)

然后 .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");
}
Run Code Online (Sandbox Code Playgroud)

如果头文件中没有最后两行 - gcc 可以正常工作,但 Visual Studio 会产生错误:

 error LNK2019: unresolved external symbol "public: void __cdecl DemoT<int>::test(void)" (?test@?$DemoT@H@@QEAAXXZ) referenced in function
Run Code Online (Sandbox Code Playgroud)

如果您想通过 .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();
Run Code Online (Sandbox Code Playgroud)

使用上一个示例中的 .cpp 文件。

然而,这会给链接器带来更多麻烦,因此如果您不导出 .dll 函数,建议使用前面的示例。