从模板中分解与参数无关的代码

时间:2015-03-07 10:53:31

标签: c++ templates inline

这个问题涉及Effective C ++书中的第44项。 Scott Mayers指出,以下模板类可能会导致代码膨胀,因为反转函数不一定取决于模板参数n。不幸的是,具有不同n值的多个模板实例,例如SquareMatrix< int,5>和SquareMatrix< int,10>也会生成反转函数的多个实例,从而生成比实际应该更大的目标代码。

template<typename T, std::size_t n>
class SquareMatrix
{   
public:
   void invert()
   {
      ...
   }
};

他建议反转函数可以在基类中考虑如下。请注意,volatile var仅用于测试目的,以防止编译器优化所有内容。 SquareMatrixBase :: invert不应该做任何合理的事情。我只是想检查它的代码是否重复。

template<typename T>
class SquareMatrixBase
{
protected:
   void invert(std::size_t size)
   {
      volatile int var = size;
   }
};

template<typename T, std::size_t n>
class SquareMatrix : private SquareMatrixBase<T>
{
private:
   using SquareMatrixBase<T>::invert;

public:
   void invert()
   {
      invert(n);
   }
};

此时Scott Mayers说:

  

现在很多 - 也许全部 - SquareMatrix的成员函数都可以   对共享的非内联基类版本的简单内联调用   与所有其他矩阵保持相同类型的数据,无论如何   他们的大小。

但是,我不明白为什么编译器也不应该内联SquareMatrixBase :: invert,这会导致代码膨胀。为什么Scott Mayers谈到“对非内联基类版本的调用”?至于我现在的模板类'成员函数总是隐式有资格进行内联,除非我强迫编译器不要通过某个特定的指令这样做。

作为测试,我使用gcc和O3优化级别编译以下主要功能

int main()
{
   {
      SquareMatrix<int, 5> sm;
      sm.invert();
   }
   {
      SquareMatrix<int, 10> sm;
      sm.invert();
   }       
   return 0;
}

并且生成的目标代码清楚地显示BaseSquareMatrixBase :: invert是内联的,从而导致重复的目标代码。

0000000000400400 <main>:
class SquareMatrixBase
{
protected:
   void invert(std::size_t size)
   {
      volatile int var = size;
  400400:       c7 44 24 f8 05 00 00    movl   $0x5,-0x8(%rsp)
  400407:       00 
   {
      SquareMatrix<int, 10> sm;
      sm.invert();
   }       
   return 0;
}
  400408:       31 c0                   xor    %eax,%eax
class SquareMatrixBase
{
protected:
   void invert(std::size_t size)
   {
      volatile int var = size;
  40040a:       c7 44 24 fc 0a 00 00    movl   $0xa,-0x4(%rsp)
  400411:       00 
   {
      SquareMatrix<int, 10> sm;
      sm.invert();
   }       
   return 0;
}
  400412:       c3                      retq 

我错过了什么?

1 个答案:

答案 0 :(得分:1)

在这个具体实例中,您和编译器都希望内联对SquareMatrixBase<int>::invert()的调用,因为它太小了。使用更大的invert()函数,编译器将在内联或调用之间进行权衡 - 看看gcc对-Os的作用很有意思,例如,使用完全实现的invert() - 但是如果基类不是模板化类(或者如果您只计划支持有限数量的实例化),则可以通过在不同的编译单元中提供实现来强制解决问题。