【问题标题】:g++: merge template member function instanceg++:合并模板成员函数实例
【发布时间】:2017-07-26 11:32:13
【问题描述】:

有没有办法让模板类的相同成员函数合并到最终的二进制文件中?我有一个可能支持特定功能的类,并且依赖于它,可能需要在成员函数内部执行一些额外的步骤:

template <uint32_t features>
class Driver {
   static bool set (uint32_t value) {
      /* do something required for every feature */
      if (features & Feature::A)
         /* do special things if Feature::A */
      /* do something required for every feature */
      if (features & Feature::B || features & Feature::C)
         /* do something special for either Feature::B or Feature::C */
      return true;
   }
   /* more, similar methods */
};

稍后在代码中,我将根据实际可用功能使用Driver&lt;Feature::A&gt;::setDriver&lt;Feature::A | Feature::B&gt;::set 之类的调用。到目前为止,所有这些都运行良好,只有使用 g++-5.4.0 -std=c++11 -O3 编译的发出代码不会合并相同的方法,我想如何使用它。在这个类中有多个函数,其中有主要的公共部分,确实会增加最终二进制文件的大小。虽然对公共部分使用私有函数会减少大小的开销,但我认为阅读起来并不友好:

template <uint32_t features>
class Driver {
    static void _set_common_0 (value) {
        /* do something required for every feature */
    }
    static bool set (uin32_t value) {
        _set_common_0 (value);
        if (features & Feature::A)
            _set_special_0 (value);
        _set_common_1 (value);
        if (features & Feature::B || features & Feature::C)
            _set_special_1 (value);
        return true;
   }
};

这也会带来问题,如果在其中一个特殊部分中存在“提前退出”路径,以前可以通过简单的return 完成,现在会以某种方式从子功能升级,引入进一步检查取决于那些子函数的返回值...

我想要的是,编译器为Driver&lt;Feature::B&gt;::setDriver&lt;Feature::C&gt;::set 发出(使用时)符号,但让它们指向相同的代码位置,因为它们在代码上是相同的。知道我该怎么做吗? (最好继续使用 c++11 并且不需要来自较新标准的功能)

编辑:为了澄清,我想知道为什么gcc 没有组合相同的指令序列(此处由模板实例生成)。据我了解,-ftree-tail-merge 至少应该替换那些跳转到相同实现/代码序列的那些(例如Driver&lt;Feature::B&gt;::setDriver&lt;Feature::C&gt;::set)。

【问题讨论】:

  • 这里真的需要类模板吗?因为这不是应该使用模板的方式:您正在运行时检查编译时常量:if (features &amp; Feature::A)
  • 它并不是真正的编译时间常数。应该使用的特性是运行时依赖的,并且在编译期间是未知的。取决于运行时环境,我可以使用Feature::A,也可以不使用。 Driver&lt;X&gt;::set 调用不是直接发出的,而是由在初始化期间分配的函数指针发出的,具体取决于检测到的环境特征。

标签: c++ c++11 templates optimization g++


【解决方案1】:

从您发布的代码中,我很清楚您在滥用模板。正确的方法是定义Driver的部分特化:

template <uint32_t features>
class Driver; // There may be some default implementation, but it is not necessary

// Specialize for feature A only
template <>
class Driver<Feature::A> {
   bool set (uint32_t value) {
      /* do special things for Feature::A */
      /* do something required for every feature */
      return true;
   }
};

// Specialize for feature B only
template <>
class Driver<Feature::B> {
   bool set (uint32_t value) {
      /* do special things for Feature::B */
      /* do something required for every feature */
      return true;
   }
};

// Specialize for features A and B
template <>
class Driver<Feature::A | Feature::B> {
   bool set (uint32_t value) {
      /* do special things for Feature::A */
      /* do special things for Feature::B */
      /* do something required for every feature */
      return true;
   }
};

模板方法的好处是您不会因为检查某些功能是否启用而产生运行时开销。当然,如果有很多特征,就会有大量的特征组合;因此输出二进制文件的膨胀。这就是为什么我建议摆脱类模板并使用运行时函数参数的原因;例如,将特征传递给构造函数:

class Driver {
private:
   uint32_t features_;

public:
   Driver(uint32_t features) : features_(features) {}
   bool set (uint32_t value) {
      /* do something required for every feature */
      if (features_ & Feature::A)
         /* do special things if Feature::A */
      /* do something required for every feature */
      if (features & Feature::B || features & Feature::C)
         /* do something special for either Feature::B or Feature::C */
      return true;
   }
};

【讨论】:

  • 我没有看到您的第一个解决方案对我给定的解决方案的好处。在不断折叠之后,模板实例化的代码将是相同的 - 缺点是,您的解决方案必须多次(在每个主体中)编写公共部分。随着未来的变化,一个人可能会错过一个可能会引入问题/错误的主体。模板用于减少运行时开销,因为我知道每次调用都有哪些功能可用。我在开始时有 one 检查以选择正确的控制流。问题是,为什么gcc 不合并相同的代码部分。
  • 你是对的。这就是为什么您在这里不需要模板并且应该使用第二种解决方案的原因。如果您决定需要模板,我只是举了一个应该使用模板的例子。
  • 在您的第二个解决方案中,我引入了不需要/不需要的运行时检查。我现在已经在编译时每次调用一个我有可用功能的函数,所以这应该是可能的(或者也是可能的)。剩下的问题是,我怎样才能使gcc 组合产生相同代码的模板实例。例如。 Driver&lt;Feature::B&gt;::setDriver&lt;Feature::C&gt;::set 将是相同的,但会发出两次 - 导致两倍大小。对于你说我应该使用模板的方式:与我的相比,你的解决方案有什么好处(所以我将使用你的)?
  • 好处是不需要对特性进行运行时检查(即,像if (features &amp; Feature::A) 这样的语句)。哪些存在于您的类模板中,并使模板参数实际上毫无用处。如果您那么关心性能,您可能只希望编译器会优化您的模板实例化并删除不必要的检查,这将采取if(true)if(false) 的形式。但我会说这是一种落后的编程方式。对于 C++ if constexpr。
  • 如果你真的想坚持模板解决方案,你可以尝试在GCC中启用-Os优化选项。这可能会减少输出二进制大小。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多