[原创文章欢迎转载,但请保留作者信息]
Justin 于 2010-04-06
对于一般的函数或类来说,要做到类似下面的事情很简单:
如果两个函数有相同的实现部分,就把这共同的部分提出来放在第三个函数中供前两者调用;
如果两个类有相同的成员,就把这共同的部分提出来放在第三个类中供前两者继承或是组合(composition)。
但对于模板来说,就没有那么明显了。因为对于模板而言,一些重复的代码并没有那么直观。
template<typename Item, int price>
class Shop
{
public:
void SellIt();
//..
};
class Apple;
Shop <Apple, 50> AShop;
Shop <Apple, 50.5> AnotherShop;
AShop.SellIt();
AnotherShop.SellIt();
以上程序粗看没什么问题(当然看出来的都是好同学),而实际上SellIt()在编译后出现了两个版本,仅仅因为AShop和AnotherShop的price不一样。
非类型模板参数(non-type template parameter,在这里就是price)会导致编译时生成重复的代码(在这里是两个SellIt)。
编译器是傻孩子,只要看到模板参数有不一样就会生成分别为不同的参数生成相应的类或者函数,于是SellIt就悄悄地有了两个版本。
解决方案之一在下面:
template<typename Item>
class ShopBase
{
protected:
void SellIt(int price);
//..
};
template<typename Item, int price>
class NewShop : private ShopBase<Item>
{
private:
using ShopBase<Item>::SellIt;
public:
void SellIt() { SellIt(price); }
//..
};
SellIt被提出来放到基类ShopBase中实现,不会因为price的不同而生成不同的函数,这样重复部分就被去除了。
另外例子中有需要注意的地方还不少:
- ShopBase中的SellIt是保护类型(protected),因为它只是为了被子类调用,不希望被其他人直接调用。
- 子类在使用模板基类前,需要用到Item43中提到的三种方法之一,否则编译器找不到所指的函数。
(原文中大师同时用了using和this->的方法,我觉得是多余的,用了using就不必要再用this->了。跑去看侯捷的译作,除了翻译过来的中文,无解@#¥%)
- 从Shop::SellIt中调用ShopBase::SellIt几乎没有增加调用成本,因为Shop::SellIt是在类的声明中定义的,实际上是内联函数。(有可能根本就没有额外代价,编译器会优化掉的)
Item44的前半段就是这些了。注意看了后半段有点唏嘘:(谁说写代码是体力活?)
大师说,类型模板参数(type template parameter,如上例中的Item)也有可能造成重复的代码生成。
NewShop <int, 32> intShop;
NewShop <long, 32> longShop;
上面的代码虽然模板参数Item的输入不一样(int和long),但是由于很多平台上long和int是一样长度的,就使得傻乎乎的编译器又一次为了实际上一样的类型生成了重复的函数(两个SellIt)
这个时候就要求程序员能够意识到不同类型的数据是否为一样的存储长度(binary representation),如果是,就用对付非类型模板参数相同的方法来解决。