如何对类定义中定义的模板成员函数使用显式模板实例化?

时间:2018-03-01 10:39:14

标签: c++ templates inline

为了减少自由使用模板的大型项目中的编译时间,我使用“extern模板”(explicit template instantiation)获得了很好的结果,以防止在许多不同的模板函数中定义编制单位。

然而,令人烦恼的是它不适用于类定义中定义的成员函数。

例如,我有以下模板类:

template <typename T>
struct Foo
{
    static T doubleIt(T input)
    {
        return input * 2;
    }
};

现在,我知道Foo最常用于数字类型,因此我将其添加到标题中:

extern template struct Foo<int>;
extern template struct Foo<float>;
extern template struct Foo<double>;

在cpp文件中,添加显式实例化:

template struct Foo<int>;
template struct Foo<float>;
template struct Foo<double>;

这不起作用,因为obj文件上的dumpbin.exe告诉我:

017 00000000 SECT4  notype ()    External     | ?doubleIt@?$Foo@M@@SAMM@Z (public: static float __cdecl Foo<float>::doubleIt(float))

如果我改变我的类定义来定义函数之外类标题,那么它可以正常工作:

template <typename T>
struct Foo
{
    static T doubleIt(T input);
};

template <typename T>
T Foo::doubleIt(T input)
{
    return input * 2;
}

我们可以使用dumpbin进行验证:

017 00000000 UNDEF  notype ()    External     | ?doubleIt@?$Foo@M@@SAMM@Z (public: static float __cdecl Foo<float>::doubleIt(float))

该解决方案的问题是在类定义之外移动所有函数定义需要大量输入,特别是当您获得更多模板参数时。

我尝试过使用declspec(__ noinline),但它仍然没有正确地执行函数(并且在可能的情况下阻止函数的内联是不合需要的。)

有一件事是单独列举每个函数,就像这样,但这当然更麻烦:

extern template int Foo<int>::doubleIt(int);
extern template float Foo<float>::doubleIt(float);
extern template double Foo<double>::doubleIt(double);

我想要的是一种将函数定义保留在类定义中的方法,同时仍然允许在可能的情况下内联函数,但是当它没有内联时,只在编译单元中创建它实例化(换句话说,与在类定义之外移动函数的行为完全相同)。

2 个答案:

答案 0 :(得分:1)

你不能两种方式,为了内联编译器需要使用源代码的方法,因为该方法是内联定义的,编译器不打算将它编译成目标文件,如果它不是直接在该对象中使用(即使它在所有情况下都是内联的,它也不会作为单独的方法存在于对象中)。 编译器总是必须构建你的函数,如果它在头文件中定义,以某种方式迫使编译器在目标文件中存储该函数的副本,而不会提高性能。

答案 1 :(得分:-1)

正如已经指出的那样,你不能同时拥有extern和内联,但是关于额外的输入部分,我做了类似的事情并尝试使用预处理器将其最小化。我不确定你是否觉得有用,但为了以防万一,我会给一个带有模板函数的模板类的例子。

档案Foo.h

template<typename T1>
struct Foo
{
    void bar(T1 input)
    {
        // ...
    }

    template<typename T2>
    void baz(T1 input1, T2 input2);
};
#include <Foo.inl>

档案Foo.cc

template<typename T1>
template<typename T2>
void Foo<T1>::baz(T1 input1, T2 input2)
{
    // ...
}
#define __FOO_IMPL
#include <Foo.inl>
#undef __FOO_IMPL

档案Foo.inl

#ifdef __FOO_IMPL
#define __FOO_EXTERN
#else
#define __FOO_EXTERN extern
#endif

#define __FOO_BAZ_INST(T1, T2) \
    __FOO_EXTERN template void Foo<T1>::baz<T2>(T1, T2);

#define __FOO_INST(T1) \
    __FOO_EXTERN template struct Foo<T1>; \
    __FOO_BAZ_INST(T1, int) \
    __FOO_BAZ_INST(T1, float) \
    __FOO_BAZ_INST(T1, double) \

__FOO_INST(int)
__FOO_INST(float)
__FOO_INST(double)

#undef __FOO_INST
#undef __FOO_BAZ_INST
#undef __FOO_EXTERN

所以它仍然是一些写作,但至少你不必小心保持同步到不同的模板声明集,并且你不必明确地经历每种可能的类型组合。在我的例子中,我有一个带有两个类型参数的类模板,以及一些带有额外类型参数的成员函数模板,每个模板都有12种可能的类型。 36行优于12 3 = 1728,尽管我希望预处理器以某种方式遍历每个参数的类型列表,但无法确定如何。

作为旁注,在我的情况下,我正在编译一个DLL,我需要编译所有模板,所以实际上模板实例化/声明看起来更像__FOO_EXTERN template __FOO_API ...