模板的另一个重要缺点是其所带来的**代码膨胀**。代码膨胀可以分为两种情况:
- 源代码的增加
- 编译生成目标代码尺寸的增加
源代码的增加
虽然模板大大增加了代码的复用性,同时也降低了代码总量。但是,有时需要为同一模板提供不同情景下的不同实现,也就是模板特例机制。
但是,C++的模板特例机制有很大的局限性,就是特例必须以函数或者类为单位整体生成,对于模板特例只需要根据模板参数值答对小部分代码块进行微调的情况,就会生成重复代码。
目标代码的增加
会在每一次编译过程中,产生越来越大的文件尺寸,耗费越来越长的编译时间。
造成代码膨胀的主要原因:
- 模板实例在多个目标文件中重复存在(如今大部分编译器会在链接时删除重复的函数模板实例)
- 模板机制所导致的大量实例以及相关的函数模板实例的出现
关于第2点的例子:
template<typename T, typename N>
struct node{
//***
};
typename node<int, void>node1;
typename node<int, node1> node2;
typename node<int, node2> node3;
使用上面模板时,没增加一个节点就会新增一种类型,以致编译器不停的构造新类型以生成新类型的函数模板实例,从而增加模板文件尺寸。
怎么解决
模板虽好,不要滥用。
所谓合理利用模板,有两层含义:
- 预估模板可能生成类型的数量级。对于能自动生成新模板示例的函数模板,一定要慎重考虑其使用频度,防止新类型泛滥。
- 最大限度的降低与模板参数有关的操作量。因为每当从某类函数生成新的实例类型时,与其相关联的被调用到的函数模板也都会生成新的函数实例,增加真实的目标代码量
对于第二点,技巧如下:
- 当我们设计类模板时,很自然的将其所有的成员函数都在类模板内实现。这样,所有成员函数都为函数模板。每当类模板生成新实例时,其成员函数都有可能相应的生产新的模板实例。
- 但是如果仔细分析,会发现不会所有的成员函数的操作都与新类型有关,有些类型无关的操作,完全不必写成模板。如能将这些操作提取出来,就可以成比例的降低函数模板所生成的目标代码量,从而显著减少目标文件尺寸和编译时间
- 那么该怎么提取呢?具体做法是为目标设计一个与模板参数无关的基类,再将模板类中与模板参数无关的成员变量以及函数都提升到基类中实现。这样,各个类模板实例所调用的都是共同基类中的成员函数,就不会重复生成等价的函数实例。
看个例子:
#include <cstddef>
struct list_node_base{
list_node_base* next;
list_node_base* prev;
list_node_base(list_node_base *next = 0,
list_node_base *prev = 0):
next(next), prev(prev){}
};
template <typename T>
struct list_node : list_node_base{
typedef T value_type;
T value;
list_node(T const&value,
list_node_base * next = 0,
list_node_base * prev = 0) :
list_node_base(next, prev),
value(value){}
};
template <typename T> class list; // 前置声明
template <typename T>
class list_iterator :public std::iterator<std::bidirectional_iterator_tag, T>{
list_node_base *pos;
typedef std::iterator<std::bidirectional_iterator_tag, T> base_type;
public:
list_iterator(list_node<T> *pos) : pos(pos) {}
list_iterator() : pos() {}
list_iterator&operator++() {pos = pos->next;}
list_iterator&operator--() {pos = pos->prev;}
bool operator==(list_iterator const &right){ return pos == right.pos;}
bool operator!=(list_iterator const &right){ return pos != right.pos;}
typename base_type::reference
operator*() { return static_cast<list_node<T>*>(pos)->value;}
friend class list<T>;
};
class list_base{
protected:
list_node_base *head;
list_node_base *tail;
size_t sz;
void push_back_only(list_node_base * pos){
if(tail){
tail->next = pos;
pos->prev = tail;
tail = pos;
}else{
tail = pos;
head = pos;
pos->prev = 0;
pos->next = 0;
}
}
void pop_back_only(){
if(tail){
list_node_base *p = tail;
tail = tail->prev;
delete p;
}
}
void swap(list_base &r){
std::swap(head, r.head);
std::swap(tail, r.tail);
std::swap(sz, r.sz);
}
public:
typedef size_t size_type;
list_base() : head(0), tail(0), sz(0){}
~list_base() {while (tail) { pop_back_only();}}
size_t size() { return sz;}
void reverse(){
list_node_base *p = head;
while (p){
std::swap(p->prev, p->next);
p = p->prev;
}
std::swap(head, tail);
}
void push_back(list_node_base *pos){
push_back_only(pos);
++sz;
}
void pop_back(){
pop_back_only();
--sz;
}
void erase(list_node_base *pos){
if(pos == head){
head = head->next;
if(head) {head->prev = 0;}
else { tail = 0; }
}else if(pos == tail){
tail = tail->prev;
tail->next = 0;
}else{
pos->prev->next = pos->next;
pos->next->prev = pos->next;
}
delete pos;
--sz;
}
};
template <typename T>
class list: public list_base{
private:
typedef list_node<T> node_type;
typedef list_base base_type;
void push_back_only(T const & v){
base_type::push_back_only(new node_type(v, 0, 0));
}
public:
typedef T value_type;
typedef T& reference;
typedef const T& const_reference;
typedef list_iterator<T> iterator;
list() : list_base(){}
~list(){}
iterator begin(){ return iterator(reinterpret_cast<node_type*>(head));}
iterator end() { return iterator();}
list& push_back(T const & v){
node_type*n = new node_type(v, 0, 0);
base_type::push_back(n);
return *this;
}
list& pop_back(){
base_type::pop_back();
return *this;
}
void assign(list &r){
iterator i = begin();
iterator r_i = r.end();
if(sz < r.sz){
for(; i != end(); i++, r_i++){*i = *r_i;}
while (r_i != r.end()) push_back_only(*r_i);
sz = r.sz;
}else{
for(; r_i != r.end(); i++, r_i++){*i = *r_i;}
while (sz != r.sz) base_type::erase(tail);
}
}
void erase(iterator i){
base_type::erase(i.pos);
}
};