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

Factoring parameter-independent code out of templates

本文关键字:代码 参数 分解      更新时间:2023-10-16

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

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

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

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谈论"对非内联基类版本的调用"?到目前为止,模板类的成员函数总是隐式符合内联条件,除非我通过某些特定指令强制编译器不要这样做。

作为测试I编译器,以下主要功能具有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 

我错过了什么?

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