我有一些模板代码,我宁愿存储在一个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中使用这段代码,但我想移植到其他环境中。


当前回答

This is definitely not a nasty hack, but be aware of the fact that you will have to do it (the explicit template specialization) for every class/type you want to use with the given template. In case of MANY types requesting template instantiation there can be A LOT of lines in your .cpp file. To remedy this problem you can have a TemplateClassInst.cpp in every project you use so that you have greater control what types will be instantiated. Obviously this solution will not be perfect (aka silver bullet) as you might end up breaking the ODR :).

其他回答

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

请参阅FAQ-lite。

你的例子是正确的,但不是很容易移植。 还可以使用一种稍微干净一点的语法(如@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;

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

注意:大多数编译器/linter /代码助手不会将此检测为错误,因为根据c++标准没有错误。 但是当你把这个翻译单元链接到一个完整的可执行文件时,链接器不会找到foo<int>的定义版本。


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

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

//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函数,建议使用前面的例子。

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

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

对于本页上的其他人来说,想知道显式模板专门化(至少在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>;