这段时间在研究C++STL源码中关于迭代器的实现原理,参考了很多资料,尽管这些资料都写得很详尽,但是还是遇到不少困惑,相信不少初学者也会碰到这些问题。因此,特地写下这篇文章,将学习过程中遇到的一些疑惑逐 一解答,希望对广大初学者有所帮助。
1、源码中迭代器实现涉及的基本思想概念
(1)泛型编程
(2)函数模板及类模板
(3)模板(偏)特化
(4)trait“萃取”机制
如果初学者对这些概念没有掌握,估计很难看懂STL源码中迭代器的实现原理。于是打算从这些基本概念讲起,逐步过渡到C++ STL迭代器的实现原理。
2、函数模板与函数模板全特化
(1)函数模板全特化
上面已经提到模板(偏)特化是一个很重要的概念,下面先讲一下模板特化。模板特化分为函数模板特化与类模板特化
函数模板——函数模板特化
类模板——类模板特化
首先看一下简单的模板:

template<typename T>
void printBigger(T num1,T num2)
{
    T bigger=num1>num2?num1:num2;
    cout<<bigger<<endl;
}

上述就展示了一个函数模板,该函数模板的作用是打印两个数中较大者。因此可以如下使用:

printBigger(11,100);//打印两个整数中的较大者,此时T=int
printBigger(11.1,12.2);//打印连个浮点数中的较大者,此时T=float
printBigger(11,18.2);//非法,编译会出错。两个参数数据的类型必须一致。此时无法推断数据T的类型,因为两个数据参数类型不一致。

顺便指出,函数模板与类模板有个很大的不同是,类模板实例化是需要显示指明模板参数类型,而函数模板是根据
函数入参自动推断类型。
printBigger(11,100)中,编译器会自动推断函数的参数类型为int,不用显示指明

类的话,需要显示指明
template<typename T> 
class Test
{
    private:
    T data;
    ……//其它省略
}
使用类模板时,需要显示指明模板数据类型
Test<int> testInt;//显示指明数据类型为int
Test<double> testDouble;//显示指明数据类型为double

(2)模板特化的概念
回到主题,那么什么是模板(偏)特化?我的理解是,模板是C++的一个很重要的特性,模板使编程泛化或者通用化,比如上面的模板函数,可以接受不同类型的参数,于是该函数模板可以处理int、double、char等类型数据。而模板特化就是将某一种特殊的类型单独拿出来处理,在匹配的时候会有限匹配特化模板,形式上是原来使用模板参数类型T的位置用一个的数据类型代替。上面的printBigger函数的功能是打印两个数中的较大者,想象一下,假如我们传入两个int *,我们目的当然不是比较这两个指针的大小,而是比较这两个指针指向的整数的的大小。如果我们直接使用模板函数printBigger(ptr1,ptr2),会直接比较两个指针的大小,这显然偏离我们的本意。在这种情况下,我们可以将处理整形指针参数特化,如下:

template<typename T>
void printBigger(T num1,T num2)  //普通模板
{
    T bigger=num1>num2?num1:num2;
    cout<<bigger<<endl;
}
template<>  //因为模板特化,指明指明了函数参数类型,故模板参数类型为空
void printBigger(int* n,int* num2) //特化模板,模板参数类型T的位置用int*代替
{
    int bigger=*num1>*num2?*num1:*num2;
    cout<<bigger<<endl;
}
**当模板特化之后,调用模板函数时,会优先匹配特性化后的函数模板。**
int a=1,b=2;
int *p=&a;
int *q=&b;
printBigger(a,b);//调用普通模板
printBigger(p,q); //调用特化模板,如果没有特化模板,普通模板也能匹配,但是模板特化,会优先匹配

3、类模板全特化与偏特化
上面说完了模板特化,那么什么是模板偏特化呢?
模板偏特化,得从类模板特化说起。因为函数模板没有偏特化!!!只有类模板才有偏特化。
再回过去看看上面的例子,模板特化就是指明模板的参数类型。上面的例子printBigger()实际上是全特化,因为该函数模板有两个参数,特化之后,两个参数类型都定了,为int*,不再需要额外的模板参数形参T。偏特化,就是只指明了部分参数类型,还是需要额外的模板形参T,
如:

先看看类模板全特化
template<typename T>
class Test   //普通类模板
{
   public:
   T data;
   Test(T d):data(d){cout<<"普通类模板:"<<data<<endl;}  
};
template<> 
class Test<char>  //全特化类模板
{
   public:
   char data;
   Test(char d):data(d){cout<<"特化类模板:"<<data<<endl;} 
};
int main() {
    char a='A';
    Test<int> num(3);
    Test<char> ch(a);
   return 0;
}
输出为:
普通类模板:3
特化类模板:A

下面再看看类模板偏特化

template<typename T>
class Test  //普通类模板
{
   public:
   T data;
   Test(T d):data(d){cout<<"普通模板:"<<data<<endl;}
};
template<typename T>
class Test<T*>  //只接收指针类型参数的偏特化类模板
{
   public:
   T* data;
   Test(T* d):data(d){cout<<"指针偏特化模板:"<<*data<<endl;}  
};
template<typename T>
class Test<T&>  //只接收引用类型的偏特化模板
{
   public:
   T data;
   Test(T &d):data(d){cout<<"引用偏特化模板:"<<data<<endl;}
};
int main() {
    int a=3;
    Test<int> num(a);
    Test<int*> pnum(&a);
    Test<int&> rnum(a);
   return 0;
}
输出如下:
普通模板:3
指针偏特化模板:3
引用偏特化模板:3

到这里,已经讲完了函数模板的特化以及类模板的全特化与偏特化。通过上面的例子,不知大家有没有明白普通模板、模板全特化与模板偏特化的区别?有以下几点可以帮助大家理解:
(1)无论时模板特化还是偏特化,都是相对于普通模板来说的
(2)声明普通模板时,需要用到类型参数T;所谓特化,要么是指明具体数据类型,如int,要么是指明参数格式,如指针,引用。如果特化后,不需要用到类型参数T,template<> 尖括号中类型参数为空,那么则是全特化,否则是偏特化
(3)从(2)可知,函数模板特化实际上是全特化,当然函数模板也不支持偏特化

4、总结于思考

函数模板只有全特化,类模板有全特化和偏特化
1、函数模板与函数模板全特化
template<typename T>
void func(T t)  //普通函数模板
{
//do something
}
template<>   //因为是全特化,所以这里不需要类型参数T
void func(int t) //全特化函数模板
{
//do something special
}
2、类模板全特化与偏特化
(1)类模板全特化
template<typename T>
class Test    //普通类模板
{
  //do something
};
template<>  //由于是全特化,不要模板类型T
class Test<int>    //全特化类模板,用int指明类实例类型
{
  //do something
};2)类模板偏特化有三种类型
类型一:
template<typename T1,typename T2>
class Test    //普通类模板
{
  //do something
};
template<typename T>  //由于是偏特化,两个类型指定了一个,还需要一个类型T
class Test<int,T>    //偏特化类模板,用int指明其中一个参数类型
{
  //do something
};
类型二:
template<typename T>
class Test    //普通类模板
{
  //do something
};
template<T>  //
class Test<T*>    //接收指针类型参数偏特化类模板,指明参数类型为指针
{
  //do something
};
template<T>  //
class Test<T&>    //接收引用类型参数偏特化类模板,指明参数类型为引用
{
  //do something
};
类型三:
template<typename T>
class Test    //普通类模板
{
  //do something
};
template<typename T>
class Test<vector<T>>    //偏特化,接收T实例化的vector对象作为实参
{
  //do something
};

思考:
(1)为什么要用模板?
让函数或者类能够适用不同的数据类型,函数或者类具有通用性。避免同样功能或结构的代码因为数据类型不同反复定义。(泛型编程)
(2)模板特化的意义?
编程的时候,我们一方面希望代码具有通用性,比如printBigger函数,我们希望对int 、double等类型都都适用。另一方面,我们又希望参数为某些类型时能够做一些特殊的处理,比如printBigger函数,当参数类型为int*,我们希望不是比较两个指针的大小而是比较指针指向对象的大小。由于模板特化之后,会优先匹配,故可以达到这样目的。
(3)函数模板全特化与函数重载的区别?
自己思考去吧~~~

参考文章:
【1】C++ 模板偏特化-来自STL的思考 https://wwwblogs/yyehl/p/7253254.html

更多推荐

C++STL迭代器实现原理之一:模板与模板特化