C++之旅(学习笔记)第7章 模板
C++之旅(学习笔记)第7章 模板
C++之旅(学习笔记)第7章 模板
模板是一个类或者一个函数,我们用一组类型或值对其进行参数化。
7.1 参数化类型
template <typename T>
class Vector {
private:T* elem;int sz;
public:explicit Vector(int s);~Vector(){ delete[] elem;}T& operator[](int i); //对于非常量的Vectorconst T& operator[](int i) const; //对于常量Vectorint size() const {return sz;}
}
前缀template <typename T>
指明T是该声明的形参。在引出类型参数时,使用class和使用typename是等价的,在旧式代码中经常出现将template <class T>
作为前缀。
成员函数的定义方式与之类似:
template<typename T>
Vector<T>::Vector(int s)
{if(s < 0)throw length_error{"Vector constructor: negative size"};elem = new T[s];sz = s;
}
template<typename T>
const T& Vector<T>::operator[](int i) const
{if(i < 0 || size() <= i)throw out_of_range{"Vector::operator[]"};return elem[i];
}
可以用如下方式声明动态数组Vector:
Vector<char> vc(200); // 200个字符组成的动态数组
Vector<string> vs(17); // 17个字符串组成的动态数组
Vector<list<int>> vli(45); // 45个整数链表组成的动态数组
为了让Vector支持范围for循环,需要为之定义适当的begin()和end()函数:
template<typename T>
T* begin(Vector<T>& x)
{return &x[0]; // 指向第一个元素,或者指向末尾元素后面的一个位置
}
template<typename T>
T* end(Vector<T>& x)
{return &x[0]+x.size(); // 指向末尾元素后面的一个位置
}
- 模板是一种编译时的机制,因此与“手工编码”相比,它并不会产生任何额外的运行时开销。
- 模板加上一系列模板参数被统称为实例化或者特例化。在编译过程中进行实例化时,每个实例都会生成一份代码。
7.2 受限模板参数
Vector的模板参数仅仅是一个typename还不够,还需要指定Element满足特定需求才能成为其元素:
template<Element T>
class Vector {
private:T* elem;int sz;// ...
};
这里,template<Element T>
Element是一个谓词,用于检查T是否满足Vector需要的特性。
这种谓词叫做概念。在模板参数中指定一个概念,这叫作受限模板参数,拥有这种参数的模板叫作受限模板。
7.2.1 模板参数值
除了类型参数以外,模板还支持值作为参数。
template<typename T, int N>
struct Buffer{constexpr int size() {return N;}T elem[N];// ...
};
值参数在很多环境中都有用。例如:Buffer允许我们创建任意尺寸的缓冲区而不需要使用动态内存分配:
Buffer<char,1024> glob; // 静态分配的全局char缓冲区
void fct()
{Buffer<int,10> buf; // 栈上分配的本地int缓冲区// ...
}
但是,字符串字面量不可以作为模板值参数。但我们可以使用存放字符的数组来表示字符串:
template<char* s>
void outs()
{ cout << s;
}char arr[] = "Werid workaround";
void use()
{outs <"straightforward use">(); // 到目前为止,这样不可行outs<arr>(); // 诡异的间接解决方案
}
7.2.2 模板参数推导
指定模板参数类型显得有些冗长,考虑使用标准库模板pair:
pair<int,double> p = {1, 5.2};
// 等价于
pair p = {1, 5.2};
可以使用s后缀把字符串变成一个正确的string
template<typename T>
class Vector {
public:Vector(int);Vector(initializer_list<T>); // 初始化列表构造函数// ...
};Vector<string> vs {"Hello", "World"}; // 可行:Vector<string>
Vector vs1 {"Hello"s, "World"s}; // 可行:推导为Vector<string>
Vector vs2 {"Hello"s, "World"}; // 错误:初始化列表中的数据类型不一致
7.3 参数化操作
模板还被广泛用于参数化标准库中的类型与算法。
要想表达将操作用类型或者值来参数化,有三种方法:
- 模板函数。
- 函数对象:对象可以携带数据,并且以函数的形式调用。
- 匿名函数表达式:函数对象的简略记法。
模板函数可以是成员函数,但不能是virtual函数。编译器不可能知道模板的所有实例,所以不可能像函数一样被调用。
7.4 函数对象
一种特别有用的模板叫作函数对象(有时也被称为仿函数),它可以用来定义对象,该对象可以像函数一样被调用。
template<typename T>
class Less_than{const T val;
public:Less_than(const T& v):val(v){}bool operator()(const T& x) const {return x < val;} //函数调用操作符
};
名叫operator()的函数实现了应用操作符(),这个操作符又可被称作“函数调用操作符”“调用操作符”。
7.5 匿名函数表达式(lambda)
匿名函数表达式(lambda)
[&](int a){ return a < x; }
[&]是匿名函数的捕获列表,它指定了函数体内所有局部变量可以以引用的形式被访问。
- 只捕获x这一个变量,使用[&x];
- 生成x的一份拷贝,使用[x];
- 什么都不捕获,使用[];
- 以引用的方式捕获所有局部变量,使用[&];
- 以值的方式捕获所有局部变量,使用[=];
- 如果成员函数中定义匿名函数,使用[this]来捕获当前对象;
- 如果成员函数中定义匿名函数,可以使用[*this]捕获当前对象的拷贝。
7.6 作用域终结函数
析构函数提供了一种通用的方案,用于在作用域结束时隐式清除所有使用过的对象RAII,但如果需要进行的清理涉及多个对象,或者涉及不含析构函数的对象,可以定义一个finally()函数,它在作用域结束时执行:
void old_style(int n)
{void *p = malloc(n* sizeof(int));auto act = finally([&]{free(p);}); // 作用域结束时调用该匿名函数
}
实现finally()函数的方法:
template<class F>
[[nodiscard]] auto finally(F f)
{return Final_action{f};
}
这里使用了[[nodiscard]]属性修饰,确保用户不会忘记保存所生成的返回值Final_action,因为正常完成功能必须保存它。
用来提供析构函数的类Final_action可以写成下面这样:
template<class F>
struct Final_action{explicit Final_action(F f):act(f){}~Final_action(){act();}F act;
};
7.7 模板机制
- 依赖类型的值:参数模板
- 类型与模板的别名:别名模板
- 编译时选择机制:if constexpr
- 编译时查询值与表达式属性的机制:requires表达式
7.8 建议
- 用模板来表达那些可以用于多种参数类型的算法;
- 用模板实现容器;
- 用模板提升代码的抽象层次;
- 让构造函数或函数模板推断出类模板实参类型;
- 把函数对象作为算法的参数;
- 如果简单的函数对象只在某处使用一次,不妨使用匿名函数;
- 不能把虚函数成员定义成模板成员函数;
- 使用finally()为不带析构函数且需要“清理操作”的类型提供RAII;
- 利用模板别名来简化符号并隐藏实现细节;
- 使用if constexpr条件编译提供替代实现,不会存在运行时开销;
- ARPG
- 2012年08月16日 Go生态洞察:优雅的代码组织之道
- 深入解析Nacos:服务发现、配置管理与更多特性解析
- https:myproject.git did not send all necessary objects
- 『MySQL快速上手』
- 侧击雷如何检测预防
- 易云维®医院能源管理系统提供多方案实现医院节能计划
- 觉非科技发布【轻地图高速NOA智驾方案】
- LeetCode——链表(Java)
- python爬虫整理
- 2023代码小技巧
- hadoop 大数据环境配置 rsync命令 hadoop(三)
- 第四章 将对象映射到 XML
- 蓝桥杯第三场双周赛(AK)
- 智能化的同城服务共享台球室小程序系统,提升台球室运营效率
- YTM32的循环冗余校验CRC外设模块详解
- logrotate 进行日志切割