以下皆在MS VS2005下测试运行。
大家都知道在标准STL里的vector容器有这样2个构造函数:
1)
vector(size_type sz,value_type val);
2)
template
vector(InputIterator first,InputIterator last);
其意义分别是:
1)初始化一个大小为sz,所有元素值都为val的vector;
2)由first和last这2个迭代器内的元素初始化vector。
大家可以试一下下面的代码,就知道它们的工作分工:
int main(){
vector
int arr[5] = {3,3,3,3,3};
vector
}
但是,如果你有兴趣自己写一个类,并且也加上这样2个构造函数,你也许会发现一点问题:
struct MyVec
{
typedef int value_type; //为了简化,这是一个int容器
typedef unsigned int size_type; //元素个数是非负数
MyVec(){} //默认构造函数
MyVec(size_type sz,value_type val){ //模仿1)
cout<<"MyVec(size_type sz,value_type val)/n";
}
template
MyVec(InputIterator first,InputIterator last){ //模仿2)
cout<<"MyVec(InputIterator first,InputIterator last)/n";
}
};
那么同样用上面的测试代码运行一下:
int main(){
MyVec v1(5,3); //调用?
int arr[5] = {3,3,3,3,3};
MyVec v2(arr,arr + 5); //调用?
}
运行结果是:
MyVec(InputIterator first,InputIterator last)
MyVec(InputIterator first,InputIterator last)
是不是看出什么问题了呢?是的,
MyVec v1(5,3);
本该调用
MyVec(size_type sz,value_type val)
可是根据C++模版参数的推导和函数匹配规则,却调用了
MyVec(InputIterator first,InputIterator last)
分析一下:5和3作为参数传给MyVec的构造函数时,是作为int类型的,即编译器会寻找和
MyVec(int,int)
最相近的构造函数,这时候选者有:
MyVec(size_type,value_type)
可是要进行一次int到unsigned int的类型转换。
还有:
MyVec(InputIterator first,InputIterator last)
这个是完全匹配,并且推导出InputIterator就是int。于是编译器决定调用后者。
这肯定不是我们要的结果。
那么怎么修正这个“bug”呢?我提供一个利用iterator_traits的解决办法,也是vc8中vector的解决方法。
首先大家应该知道iterator_traits是一个迭代器萃取机,它能萃取出迭代器的许多特性,包括iterator_category,即迭代器类型。
标准STL中迭代器的类型有5种:
struct output_iterator_tag{};
struct input_iterator_tag{};
struct forward_iterator_tag : public input_iterator_tag{};
struct bidirectional_iterator_tag : public forward_iterator_tag{};
struct random_access_iterator_tag : public bidirectional_iterator_tag{};
请注意它们的继承关系。
如果一个类型的确是一个迭代器,那么它的iterator_category必然是上述5个类型中的一个。比如任何类型的指针T*,就对应random_access_iterator_tag。
如果一个类型T不是迭代器,那么萃取它的iterator_category会得到什么呢?答案是编译错误,因为你根本没有定义T的相应iterator_category。
比如我们定义一个integer_type_tag类,并且把int的iterator_category定义为它:
struct integer_type_tag{};
template<>
iterator_traits
typedef integer_type_tag iterator_category;
};
那么我们再对int进行iterator_category萃取:
typename iterator_traits
就会得到integer_type_tag而不是原先的编译错误了。那么你现在是不是想到了什么呢了?不错,我们可以利用这个特点在
MyVec(InputIterator first,InputIterator last)
函数内再作一次转移,如果萃取出来的
typename iterator_traits
是一个integer_type_tag,那么InputIterator就不是一个迭代器,而是int,于是函数转移到MyVec(size_type,value_type)的调用:
struct MyVec
{
typedef int value_type; //为了简化,这是一个int容器
typedef unsigned int size_type; //元素个数是非负数
MyVec(){} //默认构造函数
MyVec(size_type sz,value_type val){ //模仿1)
construct_n(sz,val);
}
template
MyVec(InputIterator first,InputIterator last){ //模仿2)
typedef typename iterator_traits
construct(first,last,__IterCategory());
}
private:
template
void construct(InergerType sz,InergerType val,integer_type_tag){
construct_n(size_type(sz),value_type(val));
}
template
void construct(InputIterator first,InputIterator last,input_iterator_tag){
cout<<"construct(InputIterator first,InputIterator last)/n";
}
void construct_n(size_type sz,value_type val){
cout<<"construct_n(size_type sz,value_type val)/n";
}
};
首先我把MyVec(size_type sz,value_type val)的函数内容转移到construct_n(size_type sz,value_type val)里面,如果构造的参数完全匹配的话,就会正确调用construct_n来构造。
如果传入的参数是2个int的话,按照正常的逻辑应该也调用MyVec(size_type sz,value_type val)然后转到construct_n来构造,但是编译器会先选择
MyVec(InputIterator first,InputIterator last)
并且把InputIterator推导成int。接下来重点到了:
typedef typename iterator_traits
这句话推导出__IterCategory的类型是integer_type_tag(想想前面iterator_traits的介绍),然后调用
construct(first,last,__IterCategory())
的时候,会转移到
void construct(InergerType sz,InergerType val,integer_type_tag)
去执行,于是执行了正确的
construct_n(size_type(sz),value_type(val))。
另一方面,如果传入的参数真的是迭代器呢?
比如传入了2个int*,即:
MyVec(int *,int *);
那么编译器照样会选择
MyVec(InputIterator first,InputIterator last)
并且把InputIterator推导成int *,于是接下来的typedef会推导出__IterCategory类型为random_access_iterator_tag,而它是input_iterator_tag的子类,所以调用:
construct(first,last,__IterCategory())
会转到
construct(InputIterator first,InputIterator last,input_iterator_tag)
正确的调用了需要的函数。
在VC8中不仅把int的iterator_category进行了重定义,所有的POD类型都进行了重定义,所以如果你也这样做了,即使传入
MyVec v1(short(10),long(4));
也会调用正确的构造函数,构造出含10个4的MyVec。