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 第五版》

原文地址:https://cloud.tencent.com/developer/article/2028450

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340