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

devtools/2024/11/13 9:44:31/

2.4.2、模板参数的缺省值

        如果继续来搞模板的高度与宽度的参数,可能会想提供缺省的高度与宽度的非类型模板参数,就像前面在Grid<T>类模板的构造函数中一样。C++允许用类似的语法为模板参数提供缺省值。而我们正要这么做,那就也应该为T类型参数提供一个缺省值。下面是类定义:

export template <typename T = int, std::size_t WIDTH = 10, std::size_t HEIGHT = 10>
class Grid
{// Remainder is identical to the previous version
};

        没有在成员函数的模板头中指定T,WIDTH,HEIGHT的缺省值。例如,下面是at()的实现:

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];
}

        有了这些改变,可以在没有任何模板参数,只要有元素类型,元素类型与宽度,或元素类型,宽度与高度等的情况下实例化Grid:

import grid;
import std;using namespace std;int main()
{Grid<> myIntGrid;Grid<int> myGrid;Grid<int, 5> anotherGrid;Grid<int, 5, 5> aFourthGrid;
}

        注意如果不指定任何类模板参数,仍然需要指定一个空的<>。例如,下面的代码编译不成功!

Grid myIntGrid;

        类模板参数列表的缺省参数规则与函数一样;也就是说,可以提供从右面开始的顺序参数的缺省值。

2.4.3、类模板参数推演

        由于有了模板参数推演,编译器可以自动地从传递给灯模板构造函数的参数中推演出模板类型参数。

        例如,标准库有一个类模板叫std::pair,在<utility>中定义。pair正好保存了两个可能的不同类型的值,一般会指定为模板类型参数。举例如下:

pair<int, double> pair1 { 1, 2.3 };

        为了避免显式书写模板类型参数,辅助函数模板std::make_pair()可用。书写自己的函数模板的细节本章后面讨论。函数模板总是支持基于传递给函数模板的数值自动推演模板类型参数。这样的话,make_pair()就能够基于传递给它的值自动推演出模板类型参数。例如,对于如下的调用编译器推演出pair<int,double>:

auto pair2 { make_pair(1, 2.3) };

        有了类模板参数推演(CTAD),这样的辅助函数模板就不再需要了。编译器现在基于传递给构造函数的参数自动推演出模板类型参数。对于pair类模板,可以只写下面的代码:

pair pair3 { 1, 2.3 };    // pair3 has type pair<int, double>

        当然,只有在所有类模板的模板类型参数要么有缺省值,要么用于构造函数的参数时才有效,这样才可以推演。

        注意要让CTAD工作,必须要进行初始化。下面的代码是非法的:

pair pair4;

        许多标准库类支持CTAD,例如,vector,array,等等。

        注意:类型推演对于std::unique_ptr与shared_ptr失效。传递T*给它们的构造函数,意味着编译器不得不在推演<T>或<T[]>之间进行选择,这是一个危险的要出错的选择。所以,只要记住对于unique_ptr与shared_ptr,需要继续使用make_unique()与make_shared()。

2.4.3.1、用户定义的推演指导

        也可以书写自己的用户定义的推演指导来帮助编译器。允许书写规则如何让模板类型参数进行推演。下面是一个演示用法的例子。

        假定有SpreadsheetCell类模板:

template <typename T>
class SpreadsheetCell
{
public:explicit SpreadsheetCell(T t) : m_content{ move(t) } { }const T& getContent() const { return m_content; }
private:T m_content;
};

        由于有了CTAD,可以用std::string类型生成一个SpreadsheetCell。推演类型为SpreadsheetCell<string>:

	string myString{ "Hello World!" };SpreadsheetCell cell{ myString };

        然而,如果传递const char*给SpreadsheetCell构造函数,类型T推演为const char*,这不是你想要的!可以生成如下用户定义的推演指导确保当传递const char*作为参数时给到构造函数时能够推演出std::string:

SpreadsheetCell(const char*) -­> SpreadsheetCell<std::string>;

        该指导要定义在类定义之外,但要在与SpreadsheetCell类相同的命名空间内。

        通用语法如下。explicit关键字是可选的,与构造函数中的explicit的行为一样。这样的推演指导,总比没有强,也是模板的内容。

template <...>
explicit TemplateName(Parameters) -­> DeducedTemplate<...>;

2.5、成员函数模板

        C++允许参数化类的单个成员函数。这样的成员函数叫做成员函数模板,可以在一个正常娄或类模板内。当写成员函数模板时,实际上是在写不同类型的成员函数的许多不同版本。成员函数模板在类模板中对于赋值操作符与拷贝构造函数是有用的。

        警告:虚成员函数与析构函数不能是成员函数模板。

        考虑原来的Grid模板,只有一个模板参数:元素类型。可以实例化grid为许多不同的类型,比如int与double:

Grid<int> myIntGrid;
Grid<double> myDoubleGrid;

        然而,Grid<int>与Grid<double>是两种不同的类型。如果写一个函数使用Grid<double>类型的对象,不能传递Grid<int>。即使知道int grid元素也是可以拷贝到double grid元素中的,因为int可以被转换为double,不能将Grid<int>类型的对象赋值给Grid<double>类型的对象,或者从Grid<int>中构造Grid<double>。下面的两行代码编译不会成功:

myDoubleGrid = myIntGrid;    // DOES NOT COMPILE
Grid<double> newDoubleGrid { myIntGrid }; // DOES NOT COMPILE

        问题是Grid模板的拷贝构造函数与赋值操作符如下:

Grid(const Grid& src);
Grid& operator=(const Grid& rhs);

        等价于:

Grid(const Grid<T>& src);
Grid<T>& operator=(const Grid<T>& rhs);

        Grid拷贝构造函数与operator=两者都使用const Grid<T>的引用。当实例化Grid<double>并且尝试调用拷贝构造函数与operator=时,编译器用它们的原型来生成成员函数:

Grid(const Grid<double>& src);
Grid<double>& operator=(const Grid<double>& rhs);

        在生成的Grid<double>类中没有构造函数或operator=使用Grid<int>。

        万幸的事,可以通过添加拷贝构造函数的参数化版本与grid<int>类模板的赋值操作符来生成成员函数从一种grid类型转化为另一种来纠正这种疏忽。下面是新的Grid类模板定义:

export template <typename T>
class Grid
{
public:template <typename E>Grid(const Grid<E>& src);template <typename E>Grid& operator=(const Grid<E>& rhs);void swap(Grid& other) noexcept;// Omitted for brevity
};

        原来的拷贝构造函数与拷贝赋值操作符不能移除。编译器在E等于T时不会调用这些新的参数化的拷贝构造函数与参数化的拷贝赋值操作符。

        首先检查新的参数化的拷贝构造函数:

template <typename E>
Grid(const Grid<E>& src);

        可以看到有另外一个带有不同类型名字的模板头,E(元素的简写)。类在一种类型T在参数化,新的拷贝构造函数在另一种类型E上额外参数化。两层参数化允许拷贝一种类型的grid到另一种类型。下面是新的拷贝构造函数的定义:

template <typename T>
template <typename E>
Grid<T>::Grid(const Grid<E>& src): Grid{ src.getWidth(), src.getHeight() }
{// The ctor-initializer of this constructor delegates first to the// non-copy constructor to allocate the proper amount of memory.// The next step is to copy the data.for (std::size_t i{ 0 }; i < m_width; ++i) {for (std::size_t j{ 0 }; j < m_height; ++j) {at(i, j) = src.at(i, j);}}
}

        可以看到,在成员模板头(带有E参数)之前必须声明类模板头(带有T参数)。不能把它们组合成下面这样:

template <typename T, typename E> // Wrong for nested template constructor!
Grid<T>::Grid(const Grid<E>& src)

     在构造函数定义之前额外的模板头,注意必须使用公共的访问成员函数getWidth(),getHeight(),与at()来访问src的元素。这是因为拷贝目的的对象是Grid<T>类型,而拷贝源的对象类型为Grid<E>。它们不是同一种类型,所以必须使用公共成员函数。

        swap()成员函数就比较直接了:

template <typename T>
void Grid<T>::swap(Grid& other) noexcept
{std::swap(m_width, other.m_width);std::swap(m_height, other.m_height);std::swap(m_cells, other.m_cells);
}

        参数化的赋值操作符使用的是const Grid<E>&,但返回的是Grid<T>&:

template <typename T>
template <typename E>
Grid<T>& Grid<T>::operator=(const Grid<E>& rhs)
{// Copy-and-swap idiomGrid<T> temp{ rhs }; // Do all the work in a temporary instance.swap(temp); // Commit the work with only non-throwing operations.return *this;
}

        该赋值操作符的实现使用了copy-and-swap习语。swap()成员函数只能交换相同类型的Grid,但是因为参数化的赋值操作符首先将给定的Grid<E>转化为了Grid<T>叫做temp,使用了参数化的拷贝构造函数,也是没有问题的。这之后,它使用了swap()成员函数来用this交换这个临时的grid<T>,this也是Grid<T>类型。


http://www.ppmy.cn/devtools/133305.html

相关文章

使用亚马逊 S3 连接器为 PyTorch 和 MinIO 创建地图式数据集

在深入研究 Amazon 的 PyTorch S3 连接器之前&#xff0c;有必要介绍一下它要解决的问题。许多 AI 模型需要使用无法放入内存的数据进行训练。此外&#xff0c;许多为计算机视觉和生成式 AI 构建的真正有趣的模型使用的数据甚至无法容纳在单个服务器附带的磁盘驱动器上。解决存…

前端 call、bind、apply的实际使用

目录 一、call 1、继承的子类可以使用父类的方法 2、可以接收任意参数 二、call、apply、bind比较 1、案例一 2、案例二 三、总结 这个三个方法都是改变函数的this指向的方法。 一、call 1、继承的子类可以使用父类的方法 function Animal(){//this 指向小catthis.eat…

【用Rust写CAD】第二章 第四节 变量

文章目录 1、 变量定义2、 变量命名规则3、不可变与可变4、变量隐藏5、类型推断 1、 变量定义 如果要声明变量&#xff0c;需要使用 let 关键字。每个变量都有一个唯一的名称。 声明变量后&#xff0c;可将其绑定到某个值&#xff0c;也可稍后在程序中绑定该值。 以下代码声明…

top-k类问题

问题描述 从arr[1, n]这n个数中&#xff0c;找出最大的k个数&#xff0c;这就是经典的TopK问题。 1 直接排序 排序是最容易想到的方法&#xff0c;将n个数排序之后&#xff0c;取出最大的k个&#xff0c;即为所得&#xff1a; sort(arr, 1, n); // 时间复杂度为O(n*lg(n)) …

2025 年使用 Python 和 Go 解决 Cloudflare 问题

作为一名从事网络自动化和爬取工作的开发者&#xff0c;我亲眼目睹了日益复杂的安全性措施带来的挑战。其中一项挑战是 Cloudflare 的 Turnstile CAPTCHA 系统&#xff0c;目前该系统已在全球 2600 多万个网站上使用。这种先进的解决方案重新定义了我们对机器人检测的处理方式&…

javascript里面的blob和worker

目录 Blob 1. Blob的基本概念 2. 创建Blob 3. Blob的属性和方法 示例&#xff1a; 3.1. Blob 的方法 4. 使用Blob 4.1 创建对象URL 4.2 使用FileReader读取Blob 4.3 上传Blob 5. Blob与其他对象的关系 6. 释放Blob对象 7. Blob的应用场景 8. 总结 Web Worker 1.…

7.qsqlquerymodel 与 qtableview使用

目录 qtableview 委托QStyledItemDelegateQAbstractItemDelegateCheckBoxItemDelegate使用qtableview控制列宽&#xff0c;行高&#xff0c;隐藏拖拽行列 qtableview 委托 //设置单元格委托 void setItemDelegate(QAbstractItemDelegate *delegate); QAbstractItemDelegate *it…

【神经科学学习笔记】基于分层嵌套谱分割(Nested Spectral Partition)模型分析大脑网络整合与分离的学习总结

一、前言 1.学习背景 最近在学习脑网络分析方法时&#xff0c;笔者偶然读到了一篇发表在Physical Review Letters上的文章&#xff0c;文章介绍了一种名为嵌套谱分割(Nested-Spectral Partition, NSP)的方法&#xff0c;用于研究大脑功能网络的分离和整合特性。 传统的脑网络分…