【问题标题】:Is there the possibility of having one version of machine code for all template arguments of a templated function?是否有可能为模板化函数的所有模板参数提供一个版本的机器代码?
【发布时间】:2010-11-20 08:12:18
【问题描述】:

有些人 here 告诉这是不可能的,有些人 here 告诉它可能有一个版本的机器代码用于模板化函数的所有不同模板参数,所以我认为打开一个关于这个具体问题的线程,如果没有这种可能性就是这样,如果有,在什么情况下我们可以指望这个? 下面是两个提到的线程中特别考虑的示例:

template<size_t num>    
struct Elements{
 public:
  SomeType elements[num];
};

template<size_t num>
void print(const Elements<num> & elements,size_t size){
 //all instances do exactly same thing and with regard to Size that determines the size of object
}

当然还有传值版本:

template<size_t num>
void print(const Elements<num> elements,size_t size){
 //all instances do exactly same thing and with regard to Size that determines the size of object
}

【问题讨论】:

  • 在您的按值示例中,可能不可能将不同的实例折叠在一起,因为 elements 参数的大小会不同。这反过来会影响函数 prolog/epilog(堆栈被分配然后清理的地方)。
  • @Crashworks_所以堆栈分配不能完成为每个函数调用保留不同大小的堆栈帧的工作?
  • 您应该尝试在调试器的反汇编窗口中查看几个函数调用,看看堆栈是如何实际分配的。这可能比我能装进这个小盒子的任何东西都更有启发性。 =)

标签: c++ templates function instantiation


【解决方案1】:

智能链接器可以识别两个不同的函数体何时相同,并将它们组合成一个符号。这在 MSVC 中称为“COMDAT 折叠”,在大多数其他地方称为“重复剥离”。例如,以下两个函数可能在 PPC 上编译相同,尽管它们采用不同的类型,因为这些类型的大小相同,并且在给定情况下行为相同。

template<typename T>
GetLowBit( T foo ) { return T & 1; }

GetLowBit<unsigned long>(ulong x); // compiles to "li r4, 1 ; and r3, r3, r4 ; blr "
GetLowBit<signed long>(long x); // also compiles to "li r4, 1 ; and r3, r3, r4 ; blr "

因此链接器可以使它们的两个“名称”都指向同一个地方,因此对GetLowBit&lt;unsigned long&gt; 的调用与对GetLowBit&lt;signed long&gt; 的调用会转到相同的地址。因此,一般来说,可以将所有以相同方式在相同类型大小上运行的函数的不同模板实例折叠在一起。 (尤其是存储指针或枚举的容器往往会折叠在一起。)

这不仅仅发生在模板函数中。当任何两个函数具有相同的主体时,一些链接器可以注意到,并将它们合并。特别是我看到 MSVC 倾向于折叠每个只返回一个超级函数的虚函数,例如

class A
{ 
  virtual void nothing() {};
}

class B
{
   virtual void empty() {};
}

我经常看到(在反汇编程序中调试其他东西时)nothingempty 的 vtable 条目都指向同一个函数体,而后者又只是一个 ret

你能指望这个吗?不会。这是智能链接器可能在发现功能时为功能提供的功能。你的链接器可能很愚蠢——那里有很多糟糕的编译器。仅当您提供某些命令参数时才会发生这种情况。由于只有链接器知道的原因,某些功能可能会发生这种情况,但其他功能则不会。它甚至可能因构建而异,具体取决于程序中的其他内容。

所以总的来说,是的,这可以发生,并且当它发生时,它可以很好地节省可执行文件的大小。但是除非您非常熟悉链接器及其所有功能,否则您不能指望它会发生。

【讨论】:

    【解决方案2】:

    对于大多数普通模板,这是不可能的。使用模板的全部原因是您需要为每个模板提供单独的机器代码。在您的具体示例中,编译器实际上是在创建不同的类型,这绝对是不可折叠的,并且在按值取值的情况下(顺便说一句,这很疯狂),它需要知道该类型将占用多少空间起来。

    【讨论】:

      【解决方案3】:

      关于您的具体查询:如果我需要一个模板类,其中大多数函数不通过模板参数区分,我通常会创建一个非模板基类。这使我可以将我的大部分方法移到 .cc 文件而不是标题中,并确保函数副本不会意外激增。

      更一般地说:如果您担心自己可能会因为某个参数(您的size_t num 就是一个很好的例子)而创建模板的许多特殊实例,那么您可能已经发现了一个模板参数:应该真的是一个构造函数参数。

      【讨论】:

      • 在这种情况下,它不能是构造函数参数,因为它必须是编译时常量。
      • 如果你说是因为SomeType elements[num],是什么阻止你去SomeType *elements和动态分配?毕竟,std::vector 在没有将大小作为模板参数的情况下设法非常类似于数组。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-21
      • 1970-01-01
      • 2014-09-08
      相关资源
      最近更新 更多