【问题标题】:Separate compilation and template explicit instantiation单独的编译和模板显式实例化
【发布时间】:2014-02-03 18:07:47
【问题描述】:

总结
这个问题是关于在几个不同的翻译单元中实现单个模板类实例化的单独编译。

问题
对于非模板类,可以将定义放在几个 .cpp 文件中并分别编译它们。例如:

文件 A.h:

class A {
public:
  void func1();
  void func2();
  void func3() { /* defined in class declaration */}
}

文件 A1.cpp:

void A::func1() { /* do smth */ }

文件 A2.cpp:

void A::func2() { /* do smth else */ }

现在我尝试对模板类做类似的事情。因为我确切地知道我需要哪些实例,所以我明确地实例化了模板。我正在分别编译每个实例化,因为成员函数包含相当大的数学表达式,这可能会在高优化级别上大大降低编译器的速度。所以我尝试了以下方法:

文件 TA.h:

template <typename T>
class TA {
public:
  void func1();
  void func2();
  void func3() { /* defined in class declaration */}
}

文件 TA1.cpp:

template <typename T>
void TA<T>::func1() { /* do smth */ }
template class TA<sometype>;

文件 TA2.cpp:

template <typename T>
void TA<T>::func2() { /* do smth else */ }
template class TA<sometype>;

它适用于 Linux 上的 clang 和 GCC,但在重复符号错误期间链接期间在 Mac 上使用 GCC 失败(在此示例中,由于 func3,它在 TA1.cpp 和 TA2.cpp 中都已实例化)。

然后我在标准中偶然发现了这句话:

C++11.14.7,第 5 段:
对于给定的模板和给定的一组模板参数,
-- 显式实例化定义在程序中最多出现一次,
-- ...

这是否意味着即使使用显式实例化也不可能(不允许)单独编译模板类(隐式实例化显然不可能)?

PS 我不在乎,因为我已经得到了我的答案,但是任何认为这里有答案的人 https://stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file 是错误的。

【问题讨论】:

  • 隐式实例化显然是必要的
  • @LightnessRacesinOrbit 隐式实例化需要如何单独编译?
  • @Vayun:隐式模板实例化在使用时发生。当您在两个或多个 TU 中使用模板时,编译器已经为每个 TU 实例化了模板。编译器不可能知道其他一些 TU 已经实例化了它。因此,它必须是合法的。

标签: c++ templates explicit-instantiation


【解决方案1】:

再看标准,在我看来,唯一合理的选择是使用单个显式模板类实例化与少量“困难”函数的显式成员函数实例化相结合。

这(根据 14.7.2p9)将实例化类和到目前为止已定义的所有成员(应包括所有除了“困难”成员)。然后这些选定的成员可以在包含其定义的其他翻译单元中显式实例化。

这将使我的示例如下所示(假设 TA1.cpp 包含简单的函数,而 TA 中唯一“困难”的函数是 func2强>)

文件 TA1.cpp:

template <typename T>
void TA<T>::func1() { /* "simple" function definition */ }

template class TA<sometype>; /* expl. inst. of class */

文件 TA2.cpp:

template <typename T>
void TA<T>::func2() { /* "difficult" function definition */ }

template void TA<sometype>::func2(); /* expl. inst. of member */

这种方法需要我们为每个“难”的函数写显式的实例化定义,这很乏味,但也让我们三思而后行,是否真的要单独保留它。

免责声明

什么时候有用?不经常。正如这里的其他人所提到的,不建议将类的定义拆分到多个文件中。在我的特殊情况下,“困难”函数包含对非平凡类实例的复杂数学运算。 C++ 模板并不以编译速度快而闻名,但在这种情况下,它是难以忍受的。这些函数相互调用,从而使编译器在扩展/内联重载运算符/模板/等的漫长而消耗内存的过程中优化它看到的所有内容,几乎为零改进,但使编译持续数小时。这种将某些函数隔离在单独文件中的技巧将编译速度提高了 20 倍(并且还允许将其并行化)。

【讨论】:

    【解决方案2】:

    模板的单独编译很棘手,但允许。你不能做的是在多个翻译单元中显式实例化类型,就像你不能在两个翻译单元中定义相同的函数一样。

    【讨论】:

    • 那么如何在一个翻译单元中编译 TA::func1 而在另一个翻译单元中编译 TA::func2?
    • @Vayun:您可以触发该成员的隐式实例化,但我会避免这种情况。在不同的翻译单元中定义一个类的各个部分迟早会出现问题。至于如何触发隐式实例化,只需获取地址:/*end of file*/ void (Template&lt;Arg&gt;::*)() = &amp;Template&lt;Arg&gt;::func1; -- 我再次不会这样做。
    • 在不同的翻译单元中定义一个类的部分是完全正常的,对于非模板类不会造成任何问题。无论如何,您的回答很有帮助,但除了类的显式实例化之外,我还将使用一些成员的显式实例化。
    • @Vayun:嗯,从技术上讲,您可以将定义拆分为多个文件。但这确实带来了一些问题,导致多个编码指南(包括我们在此处遵循的)禁止它。您可以的事实并不意味着您应该这样做。
    • 感谢您的建议,但问题范围是严格的技术问题。我需要拆分定义不是因为我认为这是一种好的风格,而是因为它们包含相当大的自动生成的数学表达式,如果它们都在同一个翻译单元中,这只会扼杀优化编译器。
    猜你喜欢
    • 2012-02-29
    • 1970-01-01
    • 2020-10-13
    • 1970-01-01
    • 1970-01-01
    • 2021-11-25
    • 2021-05-02
    • 2017-06-19
    • 1970-01-01
    相关资源
    最近更新 更多