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

C++学习笔记----11、模块、头文件及各种主题(一)---- 模板概览与类模板(4)

2.2.2、显式实例化

        有危险存在于有些类模板成员函数的编译错误,在隐式实例化时没有注意到。未被使用的类模板成员函数也可能包含语法错误,因为它们不会被编译到。这会使得检测代码的语法错误很困难。可以强制编译器生成所有成员函数的代码,virtual与non-virtual,通过使用explicit template instantiations。举例如下:

template class Grid<string>;

        注意:Explicit template instantiations有助于发现错误,因为它们强制所有的类模板成员函数进行编译,即使没有被使用。

        当使用Explicit template instantiations,不要只是尝试实例化类模板的基本类型,比如int,要用更复杂的类型,比如string,如果类模板支持这些类型的话。

2.2.3、类型的模板要求

        当书写类型无关的代码时,必须假定这些类型的特定场景。例如,在Grid灯模板中,假定元素类型(用T代表)是可被析构的,copy/move可构建的,copy/move可赋值的。

        当编译器尝试用被调用的类模板成员函数不支持的操作实例化模板时,代码编译失败,错误代码通常无法辨识。然而,即使想要使用的类型不支持类模板的所有成员函数要求的操作,也可以开发选择性的实例化来使用一些成员函数而不是其它的。

        可以使用concept来书写编译器可以解释与验证的模板参数需求。编译器可以生成更多可读的错误,如果模板参数传递给不满足要求的模板实例。concept我们在本章后面讨论。

2.3、在文件之间发布模板代码

        对于类模板,类模板定义与成员函数定义必须在使用它们的任何源文件中对编译器可见。有几项技术可以完成这个要求。

2.3.1、成员函数定义在与类模板定义同一文件中

        可以将成员函数定义直接放到定义类模板自身的模块接口文件中。当在另一个使用模板的源文件中导入这个模块时,编译器具有所有需要代码的访问权限。这项技术用在了前面 Grid的实现中。

2.3.2、成员函数定义在独立的文件中

        换一种方式,可以将类模板成员函数定义放至独立的模块接口分区文件中。这样也需要将类模板定义放在自身的模块接口分区中。例如,Grid类模板的主模块接口文件可能看起来像这样:

export module grid;
export import :definition;
export import :implementation;

        导入与导出两个模块接口分区:definition与implementation。类模板定义定义在了definition分区:

export module grid:definition;
import std;
export template <typename T> class Grid { ... };

        成员函数的实现在implementation分区,也需要导入definition分区,因为它需要Grid类模板定义:

export module grid:implementation;import :definition;
import std;export template <typename T>
Grid<T>::Grid(std::size_t width, std::size_t height)
: m_width { width }, m_height { height }
{ /* ... */ }
// Remainder omitted for brevity.

2.4、模板参数

        在Grid例子中,Grid类模板有一个模板参数:在网格中保存的类型。当书写类模板时,在尖括号中指定参数,如下:

template <typename T>

        该参数与函数中的参数列表类似。与函数一样,可以书写想要的任意多的模板参数的类模板。还有,这些参数不强制为类型,可以有缺省值。

2.4.1、非类型模板参数

        非类型模板参数是“正常”参数,如int与指针--从函数来的比较熟悉的参数类型。然而,非类型模板参数只能是整型(char,int,long,等等),枚举,指针,引用,std::nullptr_t,auto&,auto*,浮点型,与类类型。后面这种,然而,会有许多限制,本文不再深入讨论。记住模板在编译时实例化;因此,非类型模板参数在编译时验证。这意味着这样的参数必须是常量或编译时常数。

        在Grid类模板中,可以使用非类型模板参数来指定网格的高度与宽度而不是在构造函数中指定。使用非类型模板参数而不使用构造函数参数的主要优势是在代码编译前其值已知。回想一下,在编译前通过替换模板参数,编译器生成模板实例的代码。这样,可以在下面的实现中使用正常的二维数组,而不是使用动态改变大小的vector来进行线性表示。下面是修改之后的新的类模板定义:

export
template <typename T, std::size_t WIDTH, std::size_t HEIGHT>
class Grid
{
public:Grid() = default;virtual ~Grid() = default;// Explicitly default a copy constructor and copy assignment operator.Grid(const Grid& src) = default;Grid& operator=(const Grid& rhs) = default;// Explicitly default a move constructor and move assignment operator.Grid(Grid&& src) = default;Grid& operator=(Grid&& rhs) = default;std::optional<T>& at(std::size_t x, std::size_t y);const std::optional<T>& at(std::size_t x, std::size_t y) const;std::size_t getHeight() const { return HEIGHT; }std::size_t getWidth() const { return WIDTH; }private:void verifyCoordinate(std::size_t x, std::size_t y) const;std::optional<T> m_cells[WIDTH][HEIGHT];
};

        现在模板参数列表有三个参数了:保存在网格中的对象类型,网格的宽度与高度。宽度与高度用于生成二维数组保存对象。下面是类模板成员函数定义:

template <typename T, std::size_t WIDTH, std::size_t HEIGHT>
void Grid<T, WIDTH, HEIGHT>::verifyCoordinate(std::size_t x, std::size_t y) const
{if (x >= WIDTH) {throw std::out_of_range { std::format("x ({}) must be less than width ({}).", x, WIDTH) };}if (y >= HEIGHT) {throw std::out_of_range { std::format("y ({}) must be less than height ({}).", y, HEIGHT) };}
}template <typename T, std::size_t WIDTH, std::size_t HEIGHT>
const std::optional<T>& Grid<T, WIDTH, HEIGHT>::at(std::size_t x, std::size_t y) const
{verifyCoordinate(x, y);return m_cells[x][y];
}template <typename T, std::size_t WIDTH, std::size_t HEIGHT>
std::optional<T>& Grid<T, WIDTH, HEIGHT>::at(std::size_t x, std::size_t y)
{return const_cast<std::optional<T>&>(std::as_const(*this).at(x, y));
}

        注意先前指定Grid<T>的地方,现在要指定Grid<T,WIDTH,HEIGHT>来指定三个模板参数。

可以实例化该模板,使用如下:

	Grid<int, 10, 10> myGrid;Grid<int, 10, 10> anotherGrid;myGrid.at(2, 3) = 42;anotherGrid = myGrid;println("{}", anotherGrid.at(2, 3).value_or(0));

        代码看起来很棒,但是不幸的是,会有比你预期的限制更多。首先,不能用非常数的整数来指定高度与宽度。下面的代码编译不成功:

size_t height { 10 };
Grid<int, 10, height> testGrid; // DOES NOT COMPILE

        如果定义height为常数,编译成功:

const size_t height { 10 };
Grid<int, 10, height> testGrid; // Compiles and works

        带有正确返回类型的constexpr函数也没总是。例如,如果有一个constexpr函数返回一个size_t,可以用它来初始化模板的height参数:

constexpr size_t getHeight() { return 10; }
...
Grid<double, 2, getHeight()> myDoubleGrid;

        第二个限制更要命。既然宽度与高度已经是模板参数了,它们就成为了每个网格类型的一部分。这意味着Grid<int,10,10>与Grid<int,10,11>是两种不同的类型。不能将一种类型的对象赋值给另一种类型的对象,也不能将一种类型的变量传递给期待另一种类型变量的函数。

        注意:非类型模板参数成为了实例化对象的类型规格的一部分。

http://www.lryc.cn/news/480033.html

相关文章:

  • 【力扣热题100】[Java版] 刷题笔记-160. 相交链表
  • 多线程和线程同步复习
  • 贝式计算的 AI4S 观察:使用机器学习对世界进行感知与推演,最大魅力在于横向扩展的有效性
  • 容器化技术入门:Docker详解
  • 基于SSM(Spring + Spring MVC + MyBatis)框架的药房管理系统
  • 在服务器里安装2个conda
  • web安全漏洞之ssrf入门
  • 《NoSQL 基础知识总结》
  • 高校宿舍信息管理系统小程序
  • 2.索引:MySQL 索引分类
  • sklearn红酒数据集分类器的构建和评估
  • 【IC验证面试常问-4】
  • 【数据集】【YOLO】【目标检测】交通事故识别数据集 8939 张,YOLO道路事故目标检测实战训练教程!
  • 书生浦语第四期基础岛L1G4000-InternLM + LlamaIndex RAG 实践
  • 基于ViT的无监督工业异常检测模型汇总
  • 数据库管理-第258期 23ai:Oracle Data Redaction(20241104)
  • 运放进阶篇-多种波形可调信号发生器-产生方波-三角波-正弦波
  • CSS中的变量应用——:root,Sass变量,JavaScript中使用Sass变量
  • WPF+MVVM案例实战与特效(二十八)- 自定义WPF ComboBox样式:打造个性化下拉菜单
  • 速盾:怎么使用cdn加速?
  • C++ 优先算法 —— 三数之和(双指针)
  • YOLOv7-0.1部分代码阅读笔记-yolo.py
  • 【缓存与加速技术实践】Web缓存代理与CDN内容分发网络
  • MySQL的约束和三大范式
  • Unity网络通信(part7.分包和黏包)
  • 练习题 - DRF 3.x Overviewses 框架概述
  • Linux 经典面试八股文
  • Filter和Listener
  • Go 项目中实现类似 Java Shiro 的权限控制中间件?
  • 【Javascript】-一些原生的网页设计案例