c++模板与泛型编程

1 定义模板

1.1 函数模板

template <typename T> // 模板参数列表,不能为空,用逗号隔开,每个类型参数前必须使用typename或class
int compare(const T &v1, const T &v2)
{
	if (v1 < v2) return -1;
	if (v2 < v1) return 1;
	return 0;
}

实例化函数模板

编译器(通常)使用函数实参的类型来确定绑定到模板参数T的类型。
例如:

cout << compare(1, 0) << endl;

实参类型是int,编译器会推断出模板实参为int,并将它绑定到模板参数T。

编译器用推断出的模板参数来为我们实例化(instantiate)一个特定版本的函数,生成的版本称为模板的实例(instantiation)。

如以上的调用生成的模板实例如下:

int compare(const int &v1, const int &v2)
{
	if (v1 < v2) return -1;
	if (v2 < v1) return 1;
	return 0;
}

也可以指定显示模板实参:

long lng = 10L;
compare<long>(lng, 1024);

模板参数

模板参数包括类型参数(type parameter)和非类型参数(nontype parameter)。我们上面的compare模板中定义的就是一个类型参数,它表示一个类型,而一个非类型模板参数则表示一个值,它通过一个特定的类型来指定。

template<unsigned N, unsigned M>
int compare(const char (&p1)[N], const char (&p2)[M])
{
	return strcmp(p1, p2);
}

调用compare("hi", "mom");时实例化(编译器会在一个字符串字面常量的末尾插入一个空字符作为终结符):

int compare(const char (&p1)[3], const char (&p2)[4]){...}

非类型参数可以是一个整型,或者是一个指向对象或函数类型的指针或(左值)引用。
非类型模板参数的模板实参必须是常量表达式。绑定到指针或引用非类型参数的实参必须具有静态的生存期,即不能用一个非static局部变量或动态对象作为指针或引用非类型模板参数的实参。

此外,为了生成一个实例化版本,编译器需要掌握函数模板或类模板成员函数的定义。因此,与非模板代码将类定义和函数声明放在头文件中而普通函数和类的成员函数的定义放在源文件中不同,模板的头文件通常既包括声明也包括定义。

1.2 类模板

template <typename T> class Blob {
public:
    typedef T value_type;
    typedef typename std::vector<T>::size_type size_type;
    // constructors
    Blob();
    Blob(std::initializer_list<T> il);
    // number of elements in the Blob
    size_type size() const { return data->size(); }
    bool empty() const { return data->empty(); }
    // add and remove elements
    void push_back(const T &t) {data->push_back(t);}
    // move version; see § 13.6.3 (p. 548)
    void push_back(T &&t) { data->push_back(std::move(t)); }
    void pop_back();
    // element access
    T& back();
    T& operator[](size_type i); // defined in § 14.5 (p. 566)
private:
    std::shared_ptr<std::vector<T>> data;
    // throws msg if data[i] isn't valid
    void check(size_type i, const std::string &msg) const;
};

类外定义成员函数的形式:

template <typename T>
ret-type Blob<T>::member-name(parm-list)

实例化类模板

Blob<int> ia;
Blob<int> ia2 = {0, 1, 2, 3, 4};

与函数模板不同,编译器不能为类模板推断模板参数类型,必须在模板名后的尖括号内加显式模板实参列表。

默认情况下,一个类模板的成员函数只有当程序用到它时才进行实例化。

在类模板自己的作用域中,我们可以直接使用模板名而不提供实参。

在类模板外使用时,类名和返回值类型必须提供模板参数,在函数体内,由于已经进入类的作用域,所以无需重复模板实参,默认与成员实例化所用类型一致。如:

// postfix: increment/decrement the object but return the unchanged value
template <typename T>
BlobPtr<T> BlobPtr<T>::operator++(int)
{
    // no check needed here; the call to prefix increment will do the check
    BlobPtr ret = *this; // save the current value
    ++*this; // advance one element; prefix ++ checks the increment
    return ret; // return the saved state
}

模板类型别名:

template<typename T> using twin = pair<T, T>;
twin<string> authors; // authors is a pair<string, string>

template <typename T> using partNo = pair<T, unsigned>; // 可以固定一个或多个模板参数

1.3 模板参数

模板参数会隐藏外层作用域中声明的相同名字,但是模板内不能重用模板参数名。

一个特定文件所需要的所有模板的声明通常一起放置在文件开始位置,出现于任何使用这些模板的代码之前。

默认情况下,C++语言假定通过作用域运算符访问的名字不是类型。因此,如果希望使用一个模板类型参数的类型成员,必须显式告诉编译器该名字是一个类型typename T::value_type。(不能使用class)

默认模板实参

// compare has a default template argument, less<T>
// and a default function argument, F()
template <typename T, typename F = less<T>>
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 T = int> class Numbers {   // by default T is int 
public:
    Numbers(T v = 0): val(v) { }
        // various operations on numbers 
private:
    T val; 
}; 
Numbers<long double> lots_of_precision; 
Numbers<> average_precision; // empty <> says we want the default type

无论何时使用类模板,都必须在模板名之后接上一对尖括号,即使有默认实参也是如此Name<> value;

1.4 成员模板

普通类的成员模板:

class DebugDelete {
public:
    DebugDelete(std::ostream &s = std::cerr): os(s) { }
    // as with any function template, the type of T is deduced by the compiler
    template <typename T> void operator()(T *p) const
    	{ os << "deleting unique_ptr" << std::endl; delete p;
}
private:
	std::ostream &os;
};

类模板的成员模板:

template <typename T> class Blob {
	template <typename It> Blob(It b, It e);
	// ...
};

在类模板外定义:

template <typename T> // type parameter for the class
template <typename It> // type parameter for the constructor
	Blob<T>::Blob(It b, It e):
		data(std::make_shared<std::vector<T>>(b, e)) { }

1.5 控制实例化

当模板被使用时才会进行实例化这一特性意味着,相同的实例可能出现在多个对象文件中。当两个或多个独立编译的源文件使用了相同的模板,并提供了相同的模板参数时,每个文件中就都会有该模板的一个实例。

在新标准中,可以通过显式实例化来避免这种开销。形式如下:

// instantion declaration and definition
extern template class Blob<string>; // declaration
template int compare(const int&, const int&); // definition

当编译器遇到extern模板声明时,它不会在本文件中生成实例化代码。将一个实例化声明为extern就表示承诺在程序其他位置有该实例化的一个非extern声明(定义)。对于一个给定的实例化版本,可能有多个extern声明,但必须只有一个定义。

// Application.cc 
// these template types must be instantiated elsewhere in the program 
extern template class Blob<string>; 
extern template int compare(const int&, const int&); 
Blob<string> sa1, sa2; // instantiation will appear elsewhere 
// Blob<int> and its initializer_list constructor instantiated in this file 
Blob<int> a1 = {0,1,2,3,4,5,6,7,8,9}; 
Blob<int> a2(a1);  // copy constructor instantiated in this file 
int i = compare(a1[0], a2[0]); // instantiation will appear elsewhere

文件Application.o将包含Blob<int>的实例及其接受initializer_list参数的构造函数和拷贝构造函数的实例。而compare<int>函数和Blob<string>类将不在本文件中进行实例化。这些模板的定义必须出现在程序的其他文件中:

// templateBuild.cc 
// instantiation file must provide a (nonextern) definition for every 
// type and function that other files declare as extern
template int compare(const int&, const int&); 
template class Blob<string>; // instantiates all members of the class template

一个类模板的实例化定义会实例化该模板的所有成员,因此,我们用来显式实例化一个类模板的类型,必须能用于模板的所有成员。

1.6 效率与灵活性

unique_ptr在编译时绑定删除器,避免了间接调用删除器的运行时开销。

shared_ptr在运行时绑定删除器,使用户重载删除器更为方便。

2 模板实参推断

2.1 类型转换与模板类型参数

将实参传递给带模板类型的函数形参时,能够自动应用的类型转换只有const转换及数组或函数到指针的转换。其他类型转换,如算术转换、派生类向基类的转换以及用户定义的转换,都不能应用于函数模板。

2.2 函数模板显式实参

// T1 cannot be deduced: it doesn't appear in the function parameter list
template <typename T1, typename T2, typename T3>
T1 sum(T2, T3);
// T1 is explicitly specified; T2 and T3 are inferred from the argument types
auto val3 = sum<long long>(i, lng); // long long sum(int, long)

显式模板实参按由左至右的顺序与对应的模板参数匹配,只有最右的参数显式模板实参才可以忽略,而且前提是它们可以从函数参数推断出来。

2.3 尾置返回类型与类型转换

由于尾置返回出现在参数列表之后,它可以使用函数的参数:

// a trailing return lets us declare the return type after the parameter list is seen
template <typename It>
auto fcn(It beg, It end) -> decltype(*beg)
{
    // process the range
    return *beg; // return a reference to an element from the range
}

头文件type_traits的remove_reference可以用来“去引用”:

// must use typename to use a type member of a template parameter; see § 16.1.3 (p.
670)
template <typename It>
auto fcn2(It beg, It end) ->
	typename remove_reference<decltype(*beg)>::type
{
    // process the range
    return *beg; // return a copy of an element from the range
}

2.4 函数指针和实参推断

函数模板也可以为一个函数指针赋值,编译器使用指针的类型来推断模板实参。

template <typename T> int compare(const T&, const T&);
// pf1 points to the instantiation int compare(const int&, const int&)
int (*pf1)(const int&, const int&) = compare;

当无法确定函数指针的唯一类型时,会出错,可以通过显式模板实参来消除调用歧义:

// overloaded versions of func; each takes a different function pointer type
void func(int(*)(const string&, const string&));
void func(int(*)(const int&, const int&));
func(compare); // error: which instantiation of compare?

// ok: explicitly specify which version of compare to instantiate
func(compare<int>); // passing compare(const int&, const int&)

当参数是一个函数模板实例的地址时,程序上下文必须满足:对每个模板参数,能唯一确定其类型或值。


参考:
《C++ Primer 第五版》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值