从模板中提取与参数无关的代码

Factoring parameter-independent code out of templates

此题参考《Effective C++》一书中的第44条。 Scott Mayers 指出以下模板 class 可能会导致代码膨胀,因为反转函数不一定依赖于模板参数 n。不幸的是,具有不同 n 值的多个模板实例,例如 SquareMatrix 和 SquareMatrix 也会生成反转函数的多个实例,从而生成比实际应该更大的目标代码。

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

他建议可以在基数 class 中分解出反转函数,如下所示。请注意,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 说:

now many -- maybe all -- of SquareMatrix's member functions can be simple inline calls to non-inline base class versions that are shared with all other matrices holding the same type of data, regardless of their size.

但是,我不明白为什么编译器不应该也内联 SquareMatrixBase::invert,这会导致代码膨胀。为什么 Scott Mayers 会谈论 "calls to non-inline base class versions"?就我现在而言,模板 class' 成员函数始终隐式符合内联条件,除非我通过某些特定指令强制编译器不这样做。

作为测试,我使用 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   [=13=]x5,-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   [=13=]xa,-0x4(%rsp)
  400411:       00 
   {
      SquareMatrix<int, 10> sm;
      sm.invert();
   }       
   return 0;
}
  400412:       c3                      retq 

我错过了什么?

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