当前位置: 首页 > article >正文

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

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 指向 data
        std::shared_ptr<std::vector<T>> data;
        // 👇 若 data[i] 无效, 则抛出 msg
        void 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.kler.cn/a/450998.html

相关文章:

  • 【MySQL基础篇】多表查询(隐式/显式内连接、左/右外连接、自连接查询、联合查询、标量/列/行/表子查询)
  • 详解磁盘IO、网络IO、零拷贝IO、BIO、NIO、AIO、IO多路复用(select、poll、epoll)
  • 简单了解函数递归
  • 重温设计模式--享元模式
  • 【YashanDB知识库】jdbc查询st_geometry类型的数据时抛出YAS-00101错误
  • C/C++基础知识复习(43)
  • vue3 + MapTalks实现2.5D地图的绘制
  • SQL Server数据库多主模式解决方案
  • 面试小札:Java后端闪电五连鞭_11
  • prometheus监控windows主机
  • Springboot基于Web的高校志愿者服务管理系统81559
  • Git安装及基础学习
  • Blazor 中调用 JavaScript
  • 20241224在ubuntu20.04.6下的终端分屏软件terminator的安装以及使用
  • 网络安全词云图与技术浅谈
  • deepin 安装 zookeeper
  • Git:查看分支、创建分支、合并分支
  • 【漫话机器学习系列】020.正则化强度的倒数C(Inverse of regularization strength)
  • 【CAE开发SDK】CEETRON Envision:适用于桌面端、Web端的数据可视化与分析
  • 【蓝桥杯每日一题】分糖果——DFS
  • Ftp目录整个下载
  • 如何保护你的 iOS 应用免受逆向工程攻击
  • 明厨亮灶系统
  • C++简明教程(9)(多文件编程)
  • 如何提高webpack的构建速度?
  • 设置浏览器声音或视频的自动播放策略