此问题涉及 Effective C++ 一书中的第 44 项。 Scott Mayers 指出以下模板类可能会导致代码膨胀,因为 invert 函数不一定依赖于模板参数 n。不幸的是,具有不同 n 值的多个模板实例,例如 SquareMatrix
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 说:
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 谈论“调用非内联基类版本”?就我现在而言,模板类的成员函数始终隐式符合内联条件,除非我通过某些特定指令强制编译器不这样做。
作为测试,我使用 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()
— 但如果基类不是模板化类(或者如果您只计划支持有限数量的实例化),您可以选择通过在不同的编译单元中提供实现来强制解决这个问题。
关于c++ - 从模板中提取与参数无关的代码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28914103/