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

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

2.5.1、带有非类型的模板参数的成员函数模板

        前面的HEIGHT与WIDTH整型模板参数的Grid类模板的主要问题是,高度与宽度变成了类型的一部分。这种限制妨碍 了将一个高度与宽度的网格赋值给一个不同高度与宽度的网格。在有些场景中,是需要赋值或拷贝一种大小的网格到一个不同大小的网格的。不是合目标对象成为源对象的完美克隆,而是只拷贝那些源数组中的元素匹配到目的数组中,如果源数组在任何维度上小于目标数组的话,用缺省省填充目标数组。用成员函数模板的赋值操作符与拷贝构造函数,可以精确地做到这一点,这就允许了赋值与拷贝不同大小的网格。下面是类定义:

export
template <typename T, std::size_t WIDTH = 10, std::size_t HEIGHT = 10>
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;

	template <typename E, std::size_t WIDTH2, std::size_t HEIGHT2>
	Grid(const Grid<E, WIDTH2, HEIGHT2>& src);

	template <typename E, std::size_t WIDTH2, std::size_t HEIGHT2>
	Grid& operator=(const Grid<E, WIDTH2, HEIGHT2>& rhs);

	void swap(Grid& other) noexcept;

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

        新的定义包含了成员函数模板的拷贝构造函数与赋值操作符,加一个辅助成员函数swap()。注意非参数化的拷贝构造函数与赋值操作符显式地缺省(因为有用户声明的析构函数)。它们只是拷贝可赋值m_cells从源到目标,这正是想要的两个同样大小的网格的语法。

        下面是参数化的拷贝构造函数:

template <typename T, std::size_t WIDTH, std::size_t HEIGHT>
template <typename E, std::size_t WIDTH2, std::size_t HEIGHT2>
Grid<T, WIDTH, HEIGHT>::Grid(const Grid<E, WIDTH2, HEIGHT2>& src)
{
	for (std::size_t i{ 0 }; i < WIDTH; ++i) {
		for (std::size_t j{ 0 }; j < HEIGHT; ++j) {
			if (i < WIDTH2 && j < HEIGHT2) {
				m_cells[i][j] = src.at(i, j);
			}
			else {
				m_cells[i][j].reset();
			}
		}
	}
}

        注意拷贝构造函数只是拷贝了WIDTH与HEIGHT元素的x与y维度,各自地从src,甚至如果src比目标大。如果src在任一维度小于目标,std::optional对象在额外的点上使用reset()成员函数重置。

        下面是swap()与operator=的实现:

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

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

2.5.2、使用带有Explicit对象参数的成员函数模板来避免代码重复(C++23)

        我们运行的Grid类模板的例子带有一个单独的模板类型参数T,包含了at()成员函数的两个重载,const与non-const。提醒一下:

export template <typename T>
class Grid
{
public:
    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;
    // Remainder omitted for brevity
};

        它们的实现使用了Scott Meyers的const_cast()模式来避免代码重复:

template <typename T>
const std::optional<T>& Grid<T>::at(std::size_t x, std::size_t y) const
{
	verifyCoordinate(x, y);
	return m_cells[x + y * m_width];
}

template <typename T>
std::optional<T>& Grid<T>::at(std::size_t x, std::size_t y)
{
	return const_cast<std::optional<T>&>(std::as_const(*this).at(x, y));
}

        虽然没有代码重复,仍然需要显式定义const与non-const重载。从C++23开始,可以使用explicit object parameter来避免必须显式提供两种重载。其秘诀在于将at()成员函数墨迹为成员函数模板,显式对象参数self的类型是其自身,是一个模板类型参数Self,这样就能自动推演了。这个特性叫做duducing this。下面是这样的一个声明:

export template <typename T>
class Grid
{
public:
    template <typename Self>
    auto&& at(this Self&& self, std::size_t x, std::size_t y);
    // Remainder omitted for brevity
};

        实现使用了传递引用Self&&;看下面的注意,这样的传递引用可以绑定Grid<T>&,const Grid<T>&,与Grid<T>&&。

        注意:类型Self&&的引用只是一个传递引用,当它用作函数或成员函数模板的参数时,使用Self作为它的一个模板类型参数。如果类成员函数有一个Self&&参数,但是带有类的Self模板类型参数,并且不是成员函数自身,那么 Self&&就不是一个引用传递,只是一个右值引用。这是因为编译器开始处理带有Self&&参数的成员函数时,类模板参数Self早已解析成了具体的类型,例如int,并且在当时,该成员函数的参数类型也已经被替换成了int&&。

        下面是实现。记住使用显式对象参数的成员函数体内,需要使用显式对象参数,在本例中是self,来得到访问对象的权限;没有this指针。

template <typename T>
template <typename Self>
auto&& Grid<T>::at(this Self&& self, std::size_t x, std::size_t y)
{
	self.verifyCoordinate(x, y);
	return std::forward_like<Self>(self.m_cells[x + y * self.m_width]);
}

        实现使用了C++23中引入的std::forward_like<Self>(x)。它返回一个对x的引用,使用了与Self&&类似的属性。这样,由于m_cells的元素类型为optional<T>,有如下结论:

  • 如果Self&&绑定了Grid<T>&,返回类型为optional<T>&。
  • 如果Self&&绑定了const Grid<T>&,返回类型为const optional<T>&。
  • 如果Self&&绑定了Grid<T>&&,返回类型为optional<T>&&。

        总结一下,有了成员函数模板,显式对象参数,传递引用,与forward_like()的组合,就能够声明与定义单个成员函数来提供const与non-const的实例了。


http://www.kler.cn/a/391734.html

相关文章:

  • [前端]NodeJS常见面试题目
  • Android音频架构
  • pySpark乱码
  • 随手记:简单实现纯前端文件导出(XLSX)
  • ABC334
  • 2024 年 8 个最佳 API 设计工具图文介绍
  • 显示微服务间feign调用的日志
  • 系统架构(01架构的特点,本质...)
  • 技术栈2:Git分布式版本控制工具
  • pycharm快速更换虚拟环境
  • 文档数字化采集与智能处理:图像弯曲矫正技术概述
  • golang 泛型 middleware 设计模式: 一次只做一件事
  • (已解决)Java不是内部或者外部命令,也不是可运行的程序
  • 工位管理新纪元:Spring Boot企业系统
  • Chrome使用IE内核
  • SCUI Admin + Laravel 整合
  • 网络安全-蓝队基础
  • 科学计算服务器:如何计算算力?如何提升科学研究效率?
  • 音视频入门基础:MPEG2-TS专题(4)——使用工具分析MPEG2-TS传输流
  • Python、selenium 自动化 - 实现自动上传外部文件
  • Flutter 小技巧之 OverlayPortal 实现自限性和可共享的页面图层
  • 设计模式(四)装饰器模式
  • RHEL 网络配置(Linux网络服务器 09)
  • ubuntu20.04安装anaconda与基本使用
  • 黑马程序员Docker学习【持续更新】
  • 电子商务网站之首页设计