模板偏特化 指针 模板萃取 指针

C++相关语法基础

模板特化(template specialization)

通用模板对于某种特定的类型可能是错误的,所以可能需要对特定的类型单独定义模板的实现。另一方面,可以利用特定类型的特点进行优化。

模板特化是指这样一个定义,该定义指定一个或多个模板的形参实际类型或值。 形式如下:

  • template<>
  • 模板名(可以是函数或类)加一对尖括号,尖括号中指定模板形参的实际类型。
  • 函数形参表和函数体或类定义。

示例:

1
2
3
4
5
6
7
8
9
10
11
template <class T1, class T2>
class some_template
{
  // ...
};

template <>
class some_template<string, int>
{
  // ...
};

类模板的部分特化(partial specialization)

部分特化是指针对template参数更进一步的条件限制所涉及出来的一个特化版本。 这里的更进一步的条件限制并不仅限于指定模板形参的实际类型。 编译器在实例化时,优先使用特化的版本。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
template <class T1, class T2>
class some_template
{
  // ...
};

template <class T1>
class some_template<T1, int>
{
  // ...
};

// 条件限制并不仅限于指定模板形参的实际类型
template <class T1, class T2>
class some_template<T1, T2*>
{
  // ...
};

迭代器关联类型

type_value

在使用容器时,我们经常会用到容器内部元素的类型,C++本身并不能获取变量类型。大多数算法的参数都是一些迭代器的范围,算法本身并不知道迭代器所指对象的内容。即使通过 typeid() 来获取,得到的也只能是类型名称,不能用来定义变量。

一个简单的解决方法是:迭代器的内部定义一个内部类型。

1
2
3
4
5
6
7
8
9
10
template <class T>
struct MyIter
{
  typedef T value_type;
  // ...
};

template <class T>
typename I::value_type func(I iter)
{ retrun *iter; };

STL容器都会定义 value_type 作为迭代器所指对象的类型。我们可以通过 iterator_traits 来提取这个类型。

1
2
3
4
5
6
template <class I>
struct iterator_traits
{
  // 这里就是value_type的定义
  typedef typename I::value_type value_type; 
};

注意上面的typename关键字,因为I是一个模板参数,在它被实例化之前,编译器并不知道value_type是个啥。所以必须显式地告诉编译器这是一个类型。

当我们需要知道迭代器所指对象的类型时,只需要用 typename iterator_traits<I>::value_type 即可。

但是原生的指针也是一种迭代器,它并不是一个类,我们也不能在其内部定义一个类型。 这个时候我们就需要利用C++的模板偏特化特性了。iterator_traits还有一个偏特化的版本,这个版本用来处理原生指针。

1
2
3
4
5
template <class T>
struct iterator_traits<T*>
{  
  typedef T value_type;
};

这里虽然没有直接指定特化的类型,但是进一步对类型进行了约束,即规定实参类型必须是指针,所以也是一种偏特化。

这样,我们可以使用 typename iterator_traits<int*>::value_type 得到int类型。

除此之外,还有一个版本用来处理常指针,如 const int* ,使得得到的结果是int,而不是const int。

1
2
3
4
5
template <class T>
struct iterator_traits<const T*>
{  
  typedef T value_type;
};

第一部分 为什么要有萃取技术

既然是一种智能指针,iterator也要对一个原生指针进行封装,而问题就源于此,当我们需要这个原生指针所指对象的类型的时候(例如声明变量),怎么办呢?

Case1 对于函数的局部变量

这种情况我们可以采用模版的参数推导,例如:

template <class T> void func(T iter)

如果T是某个指向特定对象的指针,那么在func中需要指针所指向对象类型的变量的时候,怎么办呢?这个还比较容易,模板的参数推导机制可以完成任务,如下:

template <class T, class U>

void func_impl(T t, U u) {

    U temp; // OK, we’ve got the type

            // The rest work of func…

}

template <class T>

void func(T t) {

    func_impl(t, *t); // forward the task to func_impl

}

通过模板的推导机制,我们轻而易举的或得了指针所指向的对象的类型,但是事情往往不那么简单。例如,如果我想把传递给func的这个指针参数所指的类型作为返回值,显然这个方法不能凑效了,这就是我们的case 2。

Case2 对于函数的返回值

尽管在func_impl中我们可以把U作为函数的返回值,但是问题是用户需要调用的是func,于是,你不可能写出下面的代码:

template <class T, class U>

U func_impl(T t, U u) {

    U temp; // OK, we’ve got the type

            // The rest work of func…

}

template <class T>

(*T) func(T t) { // !!!Wrong code

    return func_impl(t, *t); // forward the task to func_impl

}

 

int i  =10;

cout<<func(&i)<<endl; // !!! Can’t pass compile

红色的部分概念上如此正确,不过所有的编译器都会让你失望。这个问题解决起来也不难,只要做一个iterator,然后在定义的时候为其指向的对象类型制定一个别名,就好了,像下面这样:

template <class T>

struct MyIter {

    typedef T value_type; // A nested type declaration, important!!!

    T* ptr;

    MyIter(T* p = 0) : ptr(p) {}

    T& operator*() const { return *ptr; }

};

而后只要需要其指向的对象的类型,只要直接引用就好了,例如:

template <class I>

typename I::value_type func(I iter) { return *iter; }

很漂亮的解决方案,看上去一切都很完美。但是,实际上还是有问题,因为func如果是一个泛型算法,那么它也绝对要接受一个原生指针作为迭代器,但是显然,你无法让下面的代码编译通过:

int *p = new int(52);

cout<<func(p)<<endl; // !!!Is there a int::value_type?? Wrong Code here

我们的func无法支持原生指针,这显然是不能接受的。此时,template partial specialization就派上了用场。

Solution :template partial specialization是救世主

既然刚才的设计方案仍不完美,那我们就再加一个间接层,把智能指针和原生指针统统的封装起来。在讨论之前,先要澄清一下template partial specialization的含义。所谓的partial specialization和模板的默认参数是完全不同的两件事情,前者指的是当参数为某一类特定类型的时候,采用特殊的设计,也就是说是“针对template参数更进一步的条件限制所设计出来的一个特化版本”;而默认参数则是当不提供某些参数的时候,使用的一个缺省。

参考:partial specialization的语法

Template <typename T> class C<T*> {} // 为所有类型为T*的参数而准备的特殊版本

好了,下面我们就找一个专职的负责人,用来封装迭代器封装的对象类型。首先,把我们刚才的MyIter重新包装一下:

template <class I>

struct iterator_traits {

    Typedef I::value_type value_type;

}

现在,我们的func又有了新的面貌:

template <class I>

typename iterator_traits<I>::value_type func(I ite) {

    return *ite;

}

尽管这次我们的函数返回值的长度有些吓人,但是,我们的确为原生指针找到了好的解决方案。只要为原生指针提供一个偏特化的iterator_traits就OK了。如下:

template <class I>

struct iterator_traits<T*> {

    typedef T value_type;

};

下面,我们终于可以让func同时支持智能指针和原生指针了:

template <class I>

struct iterator_traits {

    Typedef I::value_type value_type;

}

template <class T>

struct iterator_traits<T*> {

    typedef T value_type;

};

 

template <class I>

typename iterator_traits<I>::value_type func(I ite) {

    return *ite;

}

 

int main() {

    MyIter<int> iter = new int(520);

    int *p = new int(520);

 

    // This time the following two statements will success

    cout<<func(iter)<<endl;

    cout<<func(p)<<endl;

    return 0;

}

但是,我们离最后的成功还有最后一步,如果,我们需要声明一个value_type类型的左值,但是却给iterator_traits传递了一个const int*,显然结果有问题,于是,为const T*也另起炉灶,准备一份小炒:

template<class T>

struct iterator_traits<const T*> {

    typedef T value_type;

}

OK ,现在万事大吉,无论是正宗迭代器,原生指针,const原生指针,我们都可以利用iterator_traits萃取出其封装的对象的类型,萃取技术由此而来。


发布了707 篇原创文章 · 获赞 48 · 访问量 97万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章