【C++】模板与泛型编程(一):定义模板,类模板

ops/2024/12/25 8:38:30/

16.1.2 类模板

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

为了使用类模板,必须在模板名后的尖括号中提供额外的信息——用来代替模板参数的模板实参列表。

定义类模板

作为一个例子,我们将实现 StrBlob 类的模板版本。将它重新命名为 Blob,不再针对 string。

类似于函数模板,类模板也以关键字 template 开始,后跟模板参数列表。

template <typename T> class Blob {public:typedef T value_type;typedef typename std::vector<T>::size_type size_type;// 👆 typedef 方法的第一个参数是内置关键字, 第二个参数是自定义关键字// 👇 构造函数Blob();Blob(std::initializer_list<T> il);// 👇 Blob 中元素的数目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:// 使用 shared_ptr 指向 datastd::shared_ptr<std::vector<T>> data;// 👇 若 data[i] 无效, 则抛出 msgvoid check(size_type i, const std::string &msg) const;
};

Blob 模板有一个名为 T 的模板参数类型,用来表示 Blob 保存的元素的类型。

实例化类模板

根据之前的学习,我们已经多次见到,到使用一个类模板时,必须提供额外的信息,最典型最常用的一个就是声明一个保存某种类型数据的 vector 容器。现在我们知道这些额外信息是显式模板实参列表,它们被绑定到模板参数。编译器使用这些模板实参来实例化特定的类。

例如:

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

当编译器从我们的 Blob 模板实例化出一个类时,它会重写 Blob 模板,将模板参数 T 的每个实例替换为给定的模板实参,本例为 int。

一个类模板的每个实例都形成一个独立的类。类型 Blob<string> 与任何其它 Blob 类型都没有关联,也不会对任何其它 Blob 类型的成员有特殊访问权限。

在模板作用域中引用模板类型

类模板用来实例化类型,而一个实例化的类型总是包含模板参数的。

类模板的成员函数

与任何其它类型相同,我们既可以在类模板内部,也可以在类模板外部为其定义成员函数,且定义在类模板内的成员函数被隐式地声明为内联函数。

类模板的成员函数本身是一个普通函数。但是,类模板的每个实例都有其自己版本的成员函数。因此,类模板的成员函数具有和模板相同的模板参数。因而,定义在类模板之外的成员函数必须以关键字 template 开始,后接类模板参数列表。

与往常一样,当我们在类外定义一个成员时,必须说明成员属于哪个类。且,从一个模板生成的类的名字中必须包含模板实参。当我们定义一个成员函数时,模板实参与模板形参相同:

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

check 和元素访问成员

我们首先定义 check 成员,它检查一个给定的索引:

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

除了类名中的不同之处以及使用了模板参数列表外,此函数与原 StrBlob 类的 check 成员完全一样。

下标运算符和 back 函数用模板参数指出返回类型,其它未变:

template <typename T>
T& Blob<T>::back() {check(0, "back on empty Blob");return data -> back();
}template <typename T>
T& Blob<T>::operator[](size_type i) {check(i, "subscript out of range");return (*data)[i];
}template <typename T>
void Blob<T>::pop_back() {check(0, "pop_back on empty Blob");data -> pop_back();
}

Blob 构造函数

与其它任何定义在类模板外的成员一样,构造函数的定义要以模板参数开始:

template <typename T>
Blob<T>::Blob(): data(std::make_shared<std::vector<T>>) { }

这段代码在作用域 Blob<T> 中定义了名为 Blob 的成员函数。

类似的,接受一个 initializaer_list 参数的构造函数将其类型参数 T 作为 initializaer_list 参数的元素类型:

template <typename T>
Blob<T>::Blob(std::initializer_list<T> il): data(std::make_shared<std::vector<T>>(il)) { }

为了使用这个构造函数,我们必须传递给它一个 initializer_list,其中的元素必须与 Blob 中的元素类型兼容:

Blob<string> articles = {"a", "an", "the"};

构造函数中的参数类型为 initializer_list<string>。列表中每个字符串字面常量隐式地转为一个 string。

类模板成员函数的实例化

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

在类代码内简化模板类名的使用

当我们使用一个类模板类型时,必须提供模板实参(即 typename T),但这一规则有一个例外:在类模板自己的作用域当中,可以直接使用模板名而不提供实参。

template <typename T> class BlobPtr {public:BlobPtr(): curr(0) { }BlobPtr(Blob<T> &a, size_t sz = 0):wptr(a.data), curr(sz) { }T& operator*() const {auto p = check(curr, "dereference past end");return (*p)[curr];}BlobPtr& operator++();BlobPtr& operator--();private:std::shared_ptr<std::vector<T>> check(std::size_t, const std::string&) const;std::weak_ptr<std::vector<T>> wptr;std::size_t curr;
};

可以看到,在operator++()的声明当中,其返回值是BlobPtr&而非BlobPtr<T>&,当我们处于一个类模板的作用域时,编译器处理模板自身引用时就好像我们提供了与模板参数匹配的实参一样。

在类模板外使用类模板名

在类模板外定义其它成员时,必须要知道的一点是,此时我们并不处于类的作用域当中,直到遇到类名才表示进入了类的作用域:

template <typename T>
BlobPtr<T> BlobPtr<T>::operator++(int) {BlobPtr ret = *this;++ *this;return ret;
}

可以看到在上述函数定义的第一行,我们已经进入了类的作用域,因此可以写成BlobPtr ret = *this;,它等价于BlobPtr<T> ret = *this;

类模板和友元

当一个类含有一个友元声明时,类与友元各自是否是模板是相互无关的。

一对一友好关系

类模板与另一个(类或函数)模板间的友好关系的最常见的形式是建立对应实例及其友元间的友好关系。例如,Blob 类应该将 BlobPtr 类和一个运算对象是 Blob 类的相等运算符定义为友元:

template <typename T> class BlobPtr;
template <typename T> class Blob;
template <typename T> 
bool operator==(const Blob<T> &, const Blob<T> &);
template <typename T> class Blob {friend class BlobPtr<T>;friend bool operator==<T>(const Blob<T>&, const Blob<T>&);
};

我们首先需要将 Blob、BlobPtr 和 operator== 声明为模板。这些声明是 operator== 函数的参数声明以及 Blob 中的友元声明所需要的

通用和特定模板友好关系

一个类也可以将另一个模板的每个实例都声明为自己的友元,或者限定特定的实例为友元。

令模板自己的类型参数为友元

在 C++ 11 标准当中,可以将模板类型参数声明为友元:

template <typename Type> class Bar {friend Type;// ... ... ... 
};

对于 Bar<Foo>,Foo 将成为 Bar<Foo> 的友元。

模板类型别名

由于模板不是一个类型,我们不能定义一个 typedef 引用一个模板,即:无法定义一个 typedef 引用 Blob<T>

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

template<typename T> using twin = pair<T, T>;
twin<string> authors;	// authors 是一个 pair<string string>

一个模板类型别名是一族类的别名:

twin<int> win_loss;	// pair<int, int>
twin<double> area;	// pair<double, double>

当我们定义一个模板类型别名时,可以固定一个或多个模板参数:

template<typename T> using partNo = pair<T, unsigned>;
partNo<string> books;	// pair<string, unsigned>

类模板的 static 成员

与任何其它类相同,类模板可以声明 static 成员:

template<typename T> class Foo {public:static std::size_t count() { return ctr; }// ... ... ...private:static std::size_t ctr;// ... ... ...
};

Foo 的每个实例都有自己的 static 成员实例(而不是通用一个静态成员)。


http://www.ppmy.cn/ops/144808.html

相关文章

[WASAPI]从Qt MultipleMedia来看WASAPI

[WASAPI] 从Qt MultipleMedia 来看WASAPI 最近在学习有关Windows上的音频驱动相关的知识&#xff0c;在正式开始说WASAPI之前&#xff0c;我想先说一说Qt的Multiple Media&#xff0c;为什么呢&#xff1f;因为Qt的MultipleMedia实际上是WASAPI的一层封装&#xff0c;它在是线…

【HarmonyOS】HarmonyOS和React Native混合开发 (一)之环境安装

【HarmonyOS】HarmonyOS和React Native混合开发 &#xff08;一&#xff09;之环境安装 一、React Native是什么&#xff1f; React Native 是一个基于 JavaScript 和 React 框架的开源框架&#xff0c;可以用到开发移动应用跨端解决方案。实现一套代码&#xff0c;在Android…

鸿蒙生态崛起:开发者机遇与挑战并存

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《热点时事》 期待您的关注 目录 引言 一、何为鸿蒙生态&#xff1f; 二、在鸿蒙生态下开发时遇到的挑战 三、对于鸿蒙生态未…

【Linux探索学习】第二十三弹——理解文件系统:认识硬件、探索文件在硬件上的存储问题

Linux学习笔记&#xff1a;https://blog.csdn.net/2301_80220607/category_12805278.html?spm1001.2014.3001.5482 前言&#xff1a; 我们前面讲过了文件的组成是由文件内容和文件属性两者组成的&#xff0c;但是我们前面接触的文件都是系统中的文件&#xff0c;都是已经在进…

systemverilog中的priority if

1 基本概念 在 SystemVerilog 中&#xff0c;priority - if是一种条件判断结构。它和普通的if - else语句类似&#xff0c;但在条件评估和错误检查方面有自己的特点&#xff0c;主要用于按顺序评估多个条件&#xff0c;并且对不符合预期的情况进行报错。报错如下两点 当所有条件…

【Java 基础】-- ArrayList 和 Linkedlist

目录 1. Java 中的 ArrayList 和 LinkedList 简介 ArrayList LinkedList 2. 相同数量级下的内存开销对比 ArrayList 的内存开销 LinkedList 的内存开销 3. 它们的速度对比 总结 1. Java 中的 ArrayList 和 LinkedList 简介 ArrayList 数据结构&#xff1a;基于动态数组…

经济学 ppt 2 部分

前言 上一次复习经济学是好久之前了&#xff0c;看了第一章的 ppt &#xff0c;好像重点就是谁是软件经济学之父。昨天老师讲了一下题型&#xff0c;20 分选择题&#xff0c; 20 分判断题&#xff0c;20 分计算题&#xff0c;6 6 8 三个计算题&#xff0c;25 分表格&#xff0…

Redis——缓存双写一致性问题

文章目录 1、情况描述2、缓存双写一致性2.1 情况讨论2.2 双检加锁2.3 数据库和缓存一致性的几种更新策略。 总结 1、情况描述 默认不存在缓存雪崩和缓存击穿情况。首先Java先查询redis&#xff0c;若redis中存在数据则直接返回数据。若redis中不存在数据&#xff0c;需要查询my…