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

时间:2008-09-22 15:55:53

标签: c++ templates

我有一些模板代码,我希望存储在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 :: strings,因此这些定义意味着应用程序将链接。

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

13 个答案:

答案 0 :(得分:194)

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

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

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

答案 1 :(得分:102)

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

答案 2 :(得分:19)

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

  

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

答案 3 :(得分:11)

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

答案 4 :(得分:8)

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

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

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

foo.h文件

// Standard header file guards omitted

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

foo.cpp文件

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

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

foo-impl.cpp文件

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

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

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

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

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

答案 5 :(得分:5)

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

答案 6 :(得分:4)

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

请参阅FAQ-lite了解相关信息。

答案 7 :(得分:3)

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

编辑:根据评论更正。

答案 8 :(得分:3)

这是定义模板功能的标准方法。我认为我阅读了三种定义模板的方法。也许是4.每个都有优点和缺点。

  1. 在类定义中定义。我根本不喜欢这样,因为我认为类定义严格仅供参考,并且应该易于阅读。但是,在类中定义模板要比在外部定义复杂得多。并不是所有的模板声明都具有相同的复杂度。此方法还使模板成为真正的模板。

  2. 在相同的标头中定义模板,但在类之外。在大多数情况下,这是我的首选方式。它使您的类定义保持整洁,模板仍然是真实的模板。但是,它需要完整的模板命名,这可能很棘手。另外,您的代码对所有人开放。但是,如果您需要代码内联,则这是唯一的方法。您也可以通过在类定义的末尾创建一个.INL文件来完成此操作。

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

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

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

答案 9 :(得分:1)

让我们举一个例子,由于某种原因,您想拥有一个模板类:

//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导出公开函数,则

template类语法是可选的,但这仅适用于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函数,建议使用前面的示例。

答案 10 :(得分:1)

以上都不是我的工作,所以这是您解决问题的方式,我的班级仅模板化了一种方法。

.h

df['C'].contains(x).any()

.cpp

class Model
{
    template <class T>
    void build(T* b, uint32_t number);
};

这避免了链接器错误,并且根本不需要调用TemporaryFunction

答案 11 :(得分:0)

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

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

答案 12 :(得分:0)

更新时间!创建内联(.inl或可能是任何其他)文件,只需复制其中的所有定义。请务必在每个函数(template <typename T, ...>)上方添加模板。现在,不是将头文件包含在内联文件中,而是执行相反的操作。在您的班级(#include "file.inl")声明之后加入内联文件

我真的不知道为什么没人提到这一点。我认为没有直接的弊端。