内联调用非内联基类函数(这到底是什么意思)

Inline calls to non-inline base class functions (what does that mean exactly)?

本文关键字:是什么 意思 调用 基类 类函数      更新时间:2023-10-16

我正在阅读一本c++书籍,我在一个关于减少模板生成的目标代码的部分(Effective c++ III by Scott Meyers)。其中一个例子是:

template <typename T, std::size_t n>
class SquareMatrix: private SquareMatrixBase<T> {
    public:
        SquareMatrix()
            : SquareMatrixBase<T>(n, 0),
              pData(new T[n*n])
        { this->setDataPtr(pData.get()); }
        ... functions ...
    private:
        boost::scoped_array<T> pData;
};

其中基类SquareMatrixBase有一个函数叫做

void invert(std::size_t matrixSize);

该书接着说:"不管数据存储在哪里从膨胀的角度来看,关键的结果是现在许多——也许是全部——的成员函数可以是简单的内联调用与所有其他矩阵共享的非内联基类版本保存相同类型的数据,而不考虑其大小。"

"内联调用非内联基类版本…"是什么意思?如果它是一个内联调用,我本以为它会把任何函数的整个基类版本放在使用内联的地方,但这将导致我所认为的相同的代码膨胀。虽然它说这是一个防止代码膨胀的好处。

如果你需要更多的背景信息,让我知道,章节很长,虽然我很努力地提供背景信息,但我可能遗漏了一些东西。

||EDIT -附加信息||

本文中使用方阵和方阵基的目的是:

SquareMatrix最初是一个独立的模板(不是派生的)。它包含一系列函数,这些函数根据模板参数n的值执行操作。因此,对于使用的每个n值(或使用的每个n, T对),本质上是每个函数的副本,作为每个参数对实例化这些函数的新模板。创建SquareMatrixBase是为了将依赖于size参数的函数移动到基类中。由于基类只使用类型参数(而不是大小)实例化,因此可以通过将派生类传递给基类构造函数的大小值传递给基类来调用基类中的函数。这意味着无论传入的std::size_t n如何,每个类型名T只有一个版本的函数(而不是每个{T, n}的组合都有一个版本的函数)。

关键是SquareMatrix::invert()是内联的,因此该函数甚至不会出现在结果代码中,而是直接调用受保护的基函数SquareMatrixBase::invert(n)

现在,由于该函数不是内联的,因此该函数只有一个实例(每种类型T),而不是每种大小n都有一个副本。这与单类设计形成鲜明对比,在单类设计中,每个n 的值都将实例化一个invert()函数

这意味着(许多)SquareMatrix类中的函数将是简单的内联函数;这些内联函数将包含对实现该功能的基类函数的非内联调用。例如,SquareMatrix可以有invert()成员,内联实现为:

void invert() {SquareMatrixBase<T>::invert(n);}

调用this应该生成与直接调用SquareMatrixBase::invert(n)完全相同的代码,但是由于n的值是模板作为编译时常量提供的,从而使调用代码和实现都不必将其作为运行时变量跟踪。

这意味着派生类将包含内联函数,这些函数将调用基类中执行实际工作的(繁重的)非内联函数。比较的两个变体是:

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

template <typename T, std::size_t n>
class SquareMatrix : SquareMatrixBase<T> {
...
  void invert() { SquareMatrixBase<T>::invert(pData.get(), n); }
...
};
template <typename T>
void SquareMatrixBase::invert(T* data, int n){
   ... heavy code ...
}

现在第二个程序每T发出一次重码,而第一个程序每T n 发出一次重码。这意味着在第二种情况下会有更多(几乎相同的)代码。

请注意,在此技巧中使用继承并不是强制性的,我个人只会使用自由函数(可能在私有命名空间中)。

编辑:内联基本上是用函数体替换函数调用(减少beta,哈哈!)从

my_matrix.invert()

你得到eg.

SquareMatrixBase<float>::invert(my_matrix.pData.get(), 3); <-- pseudocode

如果不阅读这篇文章,很难确切地说这里发生了什么,但是如果SquareMatrix是围绕模板基类SquareMatrixBase的模板"包装"类,并且SquareMatrix的目的基本上是将适当的模板参数传递给基类,以及为基类的函数提供一些非静态数据成员进行操作,那么您可以,由于SquareMatrix的成员函数基本上是调用基类中定义的函数的简单函数,因此将SquareMatrix的所有成员函数内联。另一方面,基类SquareMatrixBase将具有所有没有内联的"繁重"函数,而是包含在另一个编译的代码模块中。

例如,您提到SquareMatrixBase有一个函数叫做:
void SquareMatrixBase<T>::invert(std::size_t matrixSize);

SquareMatrix中,您可以简单地像这样调用这个函数:

template<typename T, std::size_t n>
inline void SquareMatrix<T, std::size_t n>::invert() { SquareMatrixBase<T>::invert(n); }

内联代码版本,即使它可以由许多不同类型大小实例化,也将编译为单个函数调用…"heavy"基类函数只需要为每个不同的类型实例化,而不是为每个不同大小的排列实例化该类型矩阵的可能需要用…因此,这肯定会减少代码膨胀。因此,SquareMatrixBase<T>只需要为intdouble等类型实例化。它包含了所有使用大量代码的"主要"函数。另一方面,SquareMatrix<T, std::size_t n>类不仅可以为int实例化,还可以为int和size{1,2,3,4,…}实例化。矩阵大小的每个不同模板参数将实例化一个新版本的SquareMatrix<T, std::size_t n>,但SquareMatrixBase<T>int版本将只使用一个实例化。