30.模板术语
这一条是很简单的一条,主要是明确不同程序员所指的内容的正确性。
template //T是一个模板参数
class Heap{…}; //Heap是一个模板名字
//…
Heap dHeap; //double是一个模板实参
//Heap 是一个模板id
模板名字和模板id之间的区别,前者只是一个简单的标识符,后者则是指附带有模板实参列表的模板名称。
31.泛型算法
一个泛型算法就是一个以这种方式设计的函数模板:根据使用上下文,它可以在编译期容易且有效的进行定制。我们来对比一下就知道为什么说STL是C++里面很经典的东西了~
template
void slowSort(T a[],int len)
{
for(int i=0;i
for(int j=i;j
if(a[j]
{
T tem(a[j]);
a[j]=a[i];
a[i]=tem;
}
}
上面这个顶多算是一个函数模板,简单来说还不够“泛”(比如,它假定是对数组,或一组对象,并且满足”=”操作符),其实和泛型算法还有很大的差别。我们来看看标准库里面怎样实现排序的:
template
void slowSort(For b,For e,Comp less) //Comp代表了怎样对待处理数据进行处理的函数
{
for(For i(b);i!=e;++i) //注意此处使用”!=”和”++i”
for(For j(i);j!=e;++j)
if(less(a[i],a[j]))
swap(a[j],a[i]);
}
template
void slowSort(For b,For e)
{
for(For i(b);i!=e;++i) //注意此处使用”!=”和”++i”
for(For j(i);j!=e;++j)
if(a[i]>a[j]))
swap(a[j],a[i]);
}
//…
std::list union;
//…
slowSort(union.begin(),union.end(),PopComp());
slowSort(union.begin(),union.end());
作者感慨的说,复杂的软件设计几乎总是群体努力的结果。
32.包含哨位
在产品级的C++应用中往往会用到大量的头文件 ,宁企鹅许多头文件中还包含其他头文件。在这些复杂情况中很容易就会产生头文件的重复包含,这会大大加长编译时间,所以我们往往通过宏定义来避免出现这种情况。
#ifndef HDR1_H
#define HDR1_h
//头文件的内容….
#endif
要注意这项功能只有一个头文件预处理符号(如上面的HDR1_H)的确和一个头文件关联时才会起到作用,因此,建立一个简单的、标准的命名约定,从而允许根据被守卫的头文件名来产生头文件预处理符号来说的确很有意义,尤其是对于大型的项目。
我们还可以用另外一种方式:
#ifndef HDR1_H
#include “hdr1.h”
#endif
补充:对于最近出现的编译器都会有些特殊的编译器选项来保证一个头文件在编译时只包含一次,比如VC++里面#pragma once
只要在一个头文件的开始添加上#pragma once就可保证改头文件被包含一次。
33.可选的关键字
//TODO::还有几点关于类模板的知识点,在后续的时候我会再添上….
(这点内容的知识确实是很麻烦,主要涉及到很多自己从前未注意的知识点,所以知识简单提一下,等自己看完《c++编程原理与实践》然后在回顾)
34、类模板的显示特化(explicit specialization)
为了进行特化,首先要有一个通用的版本,该通用版本成为主模板:
template class Heap;
主模板仅仅被声明为用于特化,但通常也提供定义:
template
class Heap
{
public:
void push(const T &val);
T pop();
bool empty() const {return h_.empty();}
private:
std::vector h_;
};
这个主模板通过为标准库算法heap algorithm提供一个易于使用的接口,从而实现一个堆数据结构。堆是一种线性化的树形结构,它对插入和检索操作进行了优化,将一个值压入一个堆中,实际上等于将该值插入到一个树形结构中;将一个值从堆中取出就等于移除并返回堆中的最大值。
其中的push/pop可以使用标准算法push_heap/pop_heap进行实现:
template
void Heap:push(const T &val)
{
h_.push_back(val);
std::push_heap(h_.begin(),h_.end());
}
template
T Heap::pop()
{
std::pop_heap(h_.begin(),h_.end());
T tmp(h_.back());
h_.pop_back();
return tmp;
}
但这个实现对于处理指向字符的指针时碰钉子了,对于指向字符的指针来说,这将会导致按照字符指针所指向的字符串的地址来组织堆,而不是按照字符串自身的值来组织堆。简单地说,堆将按照指针的值进行组织,而不是按照指针所指向的值进行组织。
我们可提供一个针对指向字符的指针的显示特化版本,来解决上面出现的问题:
template <>
class Heap
{
public :
void push(const char *pval);
const char * pop();
bool empty() const {return h_.empty();}
private:
std::vector h_;
};
其中模板参数列表时空的,但要特化的模板实参则附加在模板名字后面。让人惊讶的是,这个类模板显示特化版本其实并不是一个模板,因为此时没有剩下任何未指定的模板参数了。因此,类模板的显示特化通常被称为“完全特化”。
bool strLess(const char *a,const char *b)
{return strcmp(a,b)<0;}
void Heap::push(const char *pval)
{
h_.push_back(pval);
std::push_heap(h_.begin(),h_.end(),strLess);
}
Heap h1; //使用主模板
Heap h2; //使用显示特化
Heap h3; //使用主模板
注意:c++并没有要求显示特化的接口必须和主模板的借口完全匹配。
35、模板局部特化
目前c++还不能对函数模板进行特化(自己搜索了一下c++0x特性,没有发现这一点的改进),至少目前还不支持。我们现在能做的就是重载它们。
36、类模板成员特化
对于类模板显示特化和局部特化有一个常见的误解:一个特化版本会以某种方式从主模板那里“继承”一些东西。事实并非如此,对于主模板而言,类模板的完全特化或局部特化全然是单独的实体,它们不从主模板“继承”任何借口或实现。然而,我们应该为这些特化的版本添加和主模板相同的接口,这是当其他程序员在不知情的情况下所“期望的”!
这就意味着一个特化的模板,必须自己独立的实现主模板所具有的接口。
37、利用typename消除歧义
38、成员模板
39、采用template消除歧义
40、针对类型信息的特化
41、嵌入的类型信息
我们怎样才能知道一个容器中元素的类型?
template
class Seq{
public:
typedef T Elem; //元素的类型
typedef T Temp; //临时对象的类型
size_t size() const;
//…
};
在编译期可以查询到这种嵌入信息:
typedef Seq Strings;
//…
Strings::Elem aString;
这种方式对于使用标准库容器的任何用户来说都不会陌生。
42、traits
又是仅仅知道对象的类型是不够的,通常来说,有些与对象类型有关的信息对于处理对象来说是不可或缺的。
43、模板的模板参数
44、policy
45、模板实参的推导
46、重载函数模板
一个函数模板可以被其他函数模板和非模板函数重载。函数模板和费模板函数之间的主要区别在于对实参隐式转换的支持度。非模板函数允许对实参进行广泛的隐式转换,从内建转换(例如整形提升)到用户自定义转换;对于函数模板来说,由于编译器必须基于调用的实参类型来执行模板实参的推导,因此只支持一些琐碎的隐式转换,这包括涉及外层修饰(例如从T到const T,或从const T到T)、引用(从T到T&)以及从数组和函数退化的指针(如从T[42]到T *)等情景。
这种区别的效果就是:函数模板需要比非模板函数更精准的匹配。
47、SFINAE替换失败并非错误(substitution failure is not an error)
当试图使用函数模板实参推导机制在多个重载函数模板中非模板函数中进行选择时,编译器可能会尝试一些失败的特化。
注意:即使是实现很好的c++编译器,在面对在多个重载函数模板中非模板函数中进行选择时也会出现错误,所以应当避免复杂的出现。