模板与泛型编程(上)

16. 模板与泛型编程

相同点:面向对象编程(OOP)和泛型编程都能处理在编写程序时不知道类型的情况。

不同点:OOP能处理类型在程序运行之前都未知的情况;泛型编程中,在编译时就能获知类型。

16.1 定义模板

16.1.1 函数模板

​ 模板定义以关键字template开始,后跟一个模板参数列表,这是一个逗号分隔的一个或多个模板参数的列表,用 ‘<’‘>’ 包围起来。

/* 例1: */
template 
int compare(const T &v1,const T &v2)
{
	if(v1 < v2) return -1;
	if(v1 > v2) return 1;
	return 0;
}

NOTE:在模板定义中,模板列表不能为空。

​ 模板类型参数:类型参数可以用来指定返回类型或函数的参数类型,也可以用来在函数体内声明变量类型转换

​ 类型参数前必须使用关键字classtypename。这两个关键字函数相同,可以互换使用。

​ 一个非类型参数可以是一个整型,一个指向对象或函数类型的指针或(左值)引用。绑定到非类型整型参数的实参必须是一个常量表达式。绑定到指针或引用的非类型参数的实参必须是具有静态生命期(使用static修饰)。

​ inline和constexpr说明符放在模板参数列表之后,返回类型之前。

函数模板和类模板成员函数的定义通常放在头文件中。

16.1.2 类模板

​ 类模板(class template)是用来生成类的蓝图的。与函数模板的不同之处是,编译器不能为类模板推断模板参数类型

/* 例2:类模板 */
#include 		/* std::shared_ptr */
#include  		/* std::vector */
#include 		/* std::string */

template 
class Blob
{
public:
	typedef T value_type;
    typedef typename std::vector::size_type size_type;
    /* Constructor */
    Blob();
    Blob(std::initializer_list il);/* 初始化列表构造函数 */
    size_type size() const {return data->size();}
    bool empty() const {return data->empty();}
    void push_back(const T &t) {data->push_back(t);}
    void push_back(T &&t) {data->push_back(std::move(t));}
    void pop_back();
    T &back();
    T &operator[](size_type i);
private:
    std::shared_ptr> data;
	void check(size_type i, const std::string &msg) const;    
};	

一个类模板的每个实例都形成一个独立的类。

​ 当我们在类的外部定义一个成员函数的时候,模板实参与模板形参相同:

template
ret-type ClassName::member-name(parm-list)
/* 接例2:在类外定义成员函数 */
template 
Blob::Blob():data(std::make_shared>()){}

template 
Blob::Blob(std::initializer_list il):data(make_shared>(il)){}

template 
void Blob::check(size_type i,const std::string &msg) const
{
	if(i >= data->size())
		throw std::out_of_range(msg);
}

template 
T& Blob::back()
{
    check(0,"back on empty Blob");
	return data->back();
}

template 
const T& Blob::back() const
{
	check(0,"back on empty Blob");
 	return data->back();   
}

template 
T& Blob::operator[](size_type i)
{
    check(i,"subscript out of range");
    return (*data)[i];
}

template 
void Blob::pop_back()
{
	check(0,"pop_back on empty Blob");
    data->pop_back();
}

一个成员函数没有被使用,则它不会被实例化。成员函数只有在被用到的时候才进行实例化。

为了让所有实例成为友元,友元声明中必须使用与类模板本身不同的模板参数。

/* 例3:友元 */
template  class Pal;
class C{
	friend class Pal;/* 用类C实例化的Pal是C的友元 */
    //Pal2的所有实例都是C的友元,这种情况无需前置声明
    template  friend class Pal2;
};
template  class C2{
    //C2的每个实例将相同实例化的Pal声明为友元
    friend class Pal;//Pal模板声明必须在作用域之内
    //Pal2的所有实例都是C2的每个实例的友元,不需要前置声明
    template  friend class Pal2;
    //Pal3是个非模板类,它是C2所有实例的友元
    friend class Pal3;//不需要前置声明
};

C++11新标准

我们可以将模板类型参数声明为友元:

template  class Bar{
	friend type;//模板类型参数为该Bar所有实例相同的type为友元
	//....	
};

新标准允许我们为类模板定义一个类型别名:

template  using twin = std::pair;
twin authors;//== std::pair

16.1.3 模板参数

​ 模板参数名在一个特定模板参数列表中只能出现一次

模板声明

​ 模板声明必须包含模板参数;

​ 声明中的模板参数的名字不必与定义中相同;

​ 默认情况下,C++语言嘉定通过作用域运算符访问的名字不是类型。因此,如果我们希望使用一个模板类型参数的类型成员,必须显示告诉编译器该名字是一个类型。我们通过使用关键字***typename***来实现:

template 
typename T::value_type top(const T&c)
{
	if(!c.empty())
		return c.back();
	else
		return typename T::value_type();
}

C++11新特性

我们可以为函数和类模板提供默认实参。

/* 例4:默认模板实参 */
template >
int compare(const T& v1,const T& v2,F f = F())
{
	if(f(v1,v2)) return -1;
    if(f(v2,v1)) return 1;
    return 0;
}

​ 无论何时使用一个类模板,我们都必须在模板名之后接上尖括号尖括号指出类必须从一个模板实例化而来的。

template  class Numbers{
public:
	Number(T v = 0):val(v){}
private:
	T val;
};
Number<> average_pre;//必须有<>,空的也行表示的是从模板实例化而来的。
16.1.4 成员模板

​ 一个类可以包含本身是模板的成员函数。这种成员被称为成员模板(member template)成员模板不能是虚函数。

/* 该类类似于unique_ptr的默认删除器类型 */
class DebugDelete{
public:
    DebugDelete(std::ostream &s = std::cerr):os(s){}
    template  void operator()(T *p) const{
        os << "deleting unique_ptr" << std::endl;delete p;
    }
private:
    std::ostream os;
};
类模板的成员模板
/* 例5:类模板的成员模板 */
template  class Blob{
    template  Blob(It b,It e);
    //...
};
/* 与类模板的普通函数不同,成员模板是函数模板,当我们在类模板外面定义一个成员模板时,必须同时为类模板和成员模板提供模板参数列表。如下:*/
template 
template 
	Blob::Blob(It b,It e):data(std::make_shared>(b,e){}
16.1.5 控制实例化

C++11新特性

在大系统中,多个文件中实例化相同模板的额外开销可能非常严重。在新标准中,我们可以通过***显示实例化***来避免这种开销。

/* 主要形式 */
extern template declaration;	//实例化声明
template declaration;			//实例化定义
/* declaration是一个类或函数的声明 */
extern template class Blob;			//声明
template int compare(const int&,const int&);//定义

由于编译器在使用一个模板时自动对其实例化,因此extern声明必须出现在任何使用此实例化版本的代码之前。

对每个实例化声明,在程序中某个位置必须有其显示的实例化定义。

16.1.6 效率与灵活性

​ 通过在编译时绑定删除器,unique_ptr避免了间接调用删除器的运行时开销。通过在运行时绑定删除器,shared_ptr使用户重载删除器更为方便。

你可能感兴趣的:(读书笔记,C++Primer5th)