C 箴言:从模板中分离出参数无关的代码

2008-02-23 05:25:32来源:互联网 阅读 ()

新老客户大回馈,云服务器低至5折

  templates(模板)是节省时间和避免代码重复的极好方法。不必再输入20个相似的 classes,每一个包含 15 个 member functions(成员函数),您能够输入一个 class template(类模板),并让编译器实例化出您需要的 20 个 specific classes(特定类)和 300 个函数。(class template(类模板)的 member functions(成员函数)只有被使用时才会被隐式实例化,所以只有在每一个函数都被实际使用时,您才会得到全部 300 个member functions(成员函数)。)function templates(函数模板)也有相似的魅力。不必再写很多函数,您能够写一个 function templates(函数模板)并让编译器做其余的事。这不是很重要的技术吗?

  是的,不错……有时。假如您不小心,使用 templates(模板)可能导致 code bloat(代码膨胀):重复的(或几乎重复的)的代码,数据,或两者都有的二进制码。结果会使源代码看上去紧凑而整洁,但是目标代码臃肿而松散。臃肿而松散很少会成为时尚,所以您需要了解如何避免这样的二进制扩张。

  您的主要工具备一个有气势的名字 commonality and variability analysis(通用性和可变性分析),但是关于这个想法并没有什么有气势的东西。即使在您的职业生涯中从来没有使用过模板,您也应该从始至终做这样的分析。

  当您写一个函数,而且您意识到这个函数的实现的某些部分和另一个函数的实现本质上是相同的,您会仅仅复制代码吗?当然不。您从这两个函数中分离出通用的代码,放到第三个函数中,并让那两个函数来调用这个新的函数。也就是说,您分析那两个函数以找出那些通用和变化的构件,您把通用的构件移入一个新的函数,并把变化的构件保留在原函数中。类似地,假如您写一个 class,而且您意识到这个 class 的某些构件和另一个 class 的构件是相同的,您不要复制那些通用构件。作为替代,您把通用构件移入一个新的 class 中,然后您使用 inheritance(继承)或 composition(复合)使得原来的 classes 能够访问这些通用特性。原来的 classes 中不同的构件——变化的构件——仍保留在他们原来的位置。

  在写 templates(模板)时,您要做同样的分析,而且用同样的方法避免重复,但这里有一个技巧。在 non-template code(非模板代码)中,重复是显式的:您能够看到两个函数或两个类之间存在重复。在 template code(模板代码)中。重复是隐式的:仅有一份 template(模板)源代码的拷贝,所以您必须培养自己去判断在一个 template(模板)被实例化多次后可能发生的重复。

  例如,假设您要为固定大小的 square matrices(正方矩阵)写一个 templates(模板),其中,要支持 matrix inversion(矩阵转置)。

templatestd::size_t n> // objects of type T; see below for info
class SquareMatrix { // on the size_t parameter
public:
 ...
 void invert(); // invert the matrix in place
};

  这个 template(模板)取得一个 type parameter(类型参数)T,但是他更有一个类型为 size_t 的参数——一个 non-type parameter(非类型参数)。non-type parameter(非类型参数)比 type parameter(类型参数)更不通用,但是他们是完全合法的,而且,就像在本例中,他们能够很自然。

  现在考虑以下代码:

SquareMatrix sm1;
...
sm1.invert(); // call SquareMatrix::invert

SquareMatrix sm2;
...
sm2.invert(); // call SquareMatrix::invert

  这里将有两个 invert 的拷贝被实例化。这两个函数不是相同的,因为一个作用于 5 x 5 矩阵,而另一个作用于 10 x 10 矩阵,但是除了常数 5 和 10 以外,这两个函数是相同的。这是个发生 template-induced code bloat(模板导致的代码膨胀)的经典方法。

  假如您看到两个函数除了一个版本使用了 5 而另一个使用了 10 之外,对应字符全部相等,您该怎么做呢?您的直觉让您创建一个取得一个值作为一个参数的函数版本,然后用 5 或 10 调用这个参数化的函数以代替复制代码。您的直觉为您提供了很好的方法!以下是个初步过关的 SquareMatrix 的做法:

template // size-independent base class for
class SquareMatrixBase { // square matrices
protected:
 ...
 void invert(std::size_t matrixSize); // invert matrix of the given size
 ...
};

template< typename T, std::size_t n>
class SquareMatrix: private SquareMatrixBase {
private:
 using SquareMatrixBase::invert; // avoid hiding base version of
 // invert; see Item 33
public:
 ...
 void invert() { this->invert(n); } // make inline call to base class
}; // version of invert; see below
// for why "this->" is here


  就像您能看到的,invert 的参数化版本是在一个 base class(基类)SquareMatrixBase 中的。和 SquareMatrix 相同,SquareMatrixBase 是个 template(模板),但和 SquareMatrix 不相同的是,他参数化的仅仅是矩阵中的对象的类型,而没有矩阵的大小。因此,任何持有一个给定对象类型的矩阵将共享一个单一的 SquareMatrixBase class。从而,他们共享 invert 在那个 class 中的版本的单一拷贝。

  SquareMatrixBase::invert 仅仅是个计划用于 derived classes(派生类)以避免代码重复的方法,所以他是 protected 的而不是 public 的。调用他的额外成本应该为零,因为 derived classes(派生类)的 inverts 使用 inline functions(内联函数)调用 base class(基类)的版本。(这个 inline 是隐式的——参见《理解inline化的介入和排除》。)这些函数使用了 "this->" 标记,因为就像 Item 43 解释的,假如不这样,在 templatized base classes(模板化基类)中的函数名(诸如 SquareMatrixBase

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇: C 之父Bjarne谈C 中的STL模板

下一篇: C 中的动态多维数组