IT评测·应用市场-qidao123.com技术社区

标题: C++学习笔记----11、模块、头文件及各种主题(一)---- 模板概览与类模板( [打印本页]

作者: 自由的羽毛    时间: 2024-11-8 07:22
标题: C++学习笔记----11、模块、头文件及各种主题(一)---- 模板概览与类模板(
2.1.2、模板Grid类

        在上一节中的GameBoard很不错但还不敷。一个问题是不能用GameBoard来以值来保存元素;它保存的是指针。另一个更严肃的问题是,与类型安全相关。在GameBoard的每一个网格中保存了一个unique_ptr<GamePiece>。纵然是保存ChessPiece,当使用at()来请示一个特定的棋子时,会得到一个unique_ptr<GamePiece>。这就意味着不得不将查询的GamePiece向下转化为ChessPiece以便能够使用ChessPiece的特定功能。还有,无法阻止在GameBoard中混合各种不同的GamePiece继承来的对象。例如,假定不但有ChessPiece,还有TicTacToePiece:
  1. class TicTacToePiece : public GamePiece
  2. {
  3. public:
  4.     std::unique_ptr<GamePiece> clone() const override
  5.     {
  6.         // Call the copy constructor to copy this instance
  7.         return std::make_unique<TicTacToePiece>(*this);
  8.     }
  9. };
复制代码
        用上一节的多态解决方案,无法阻止在单个的游戏面板上保存井字棋棋子与国际象棋棋子:
  1. GameBoard gameBoard { 8, 8 };
  2. gameBoard.at(0, 0) = std::make_unique<ChessPiece>();
  3. gameBoard.at(0, 1) = std::make_unique<TicTacToePiece>();
复制代码
        这个大问题就是不管怎么样都要记住在特定位置保存的是什么,以便调用at()时执行正确的向下转化。
        GameBoard的一个缺点是它不能用于保存原始数据类型,好比int或double,由于在网格中的数据类型必须继承自GamePiece。
        如果写一个通用的Grid类,能够用于保存ChessPiece,SpreadsheetCell,int,double,等等,会是非常好的。在C++中,可以通过写一个类模板来轮到,它是类定义的一个蓝图。在类模板中,数据类型还不明确。客户可以在想使用时通过指定类型来实例化模板。这叫做泛型编程。泛型编程最大的好处是类型安全。在实例化类定义与成员函数中使用的类型是详细的类型,而不是上一节中使用多态解决方案的基类类型中的抽象类型。
        我们开始看如何书写这样的一个Grid类模板定义。
2.1.2.1、Grid类模板定义

        要明白类模板,研究一下其语法是很有帮助的。下面的例子展示了如何修改GameBoard类生成一个参数化的Grid类模板。代码后会对语法细节举行解释。留意名字由GameBoard修改为了Grid。Grid也应该能用于原始数据类型,如int与double。这就是为什么优选使用值语法而不是与用于GameBoard实现的多态指针语法相比的多态,来实现该解决方案。m_cells容器保存了真实的对象,而不是指针。与指针语法相比使用值语法的向下转化,使得不能有真正的空网格。也就是说,网格必须要有值。而指针语法,空网格保存了Nullptr。幸运的是,std:ptional来救场了。它允许使用值语法,同时也有表现空网格的方法。
  1. export
  2. template <typename T>
  3. class Grid
  4. {
  5. public:
  6.         explicit Grid(std::size_t width = DefaultWidth, std::size_t height = DefaultHeight);
  7.         virtual ~Grid() = default;
  8.         // Explicitly default a copy constructor and copy assignment operator.
  9.         Grid(const Grid& src) = default;
  10.         Grid& operator=(const Grid& rhs) = default;
  11.         // Explicitly default a move constructor and move assignment operator.
  12.         Grid(Grid&& src) = default;
  13.         Grid& operator=(Grid&& rhs) = default;
  14.         std::optional<T>& at(std::size_t x, std::size_t y);
  15.         const std::optional<T>& at(std::size_t x, std::size_t y) const;
  16.         std::size_t getHeight() const { return m_height; }
  17.         std::size_t getWidth() const { return m_width; }
  18.         static constexpr std::size_t DefaultWidth{ 10 };
  19.         static constexpr std::size_t DefaultHeight{ 10 };
  20. private:
  21.         void verifyCoordinate(std::size_t x, std::size_t y) const;
  22.         std::vector<std::optional<T>> m_cells;
  23.         std::size_t m_width { 0 }, m_height { 0 };
  24. };
复制代码
        现在看到了完备的类模板定义,先看第一行。
  1. export template <typename T>
复制代码
        第一行说明下面的类定义是一个类型T的模板,从模块中导出。“template <typename T>”部分叫做模板头。在C++中template与typename都是关键字。我们前面讨论过,模板”参数化”类型以与函数”参数化”值一样的方式工作。与在函数中代表调用者传递的参数一样使用参数名字,在模板中使用模板类型参数名字(好比T)来代表调用者传递的模板类型参数一样的类型。名字T没有什么特别的--可以使用想用的任意名字。传统意义上,当单个类型使用时,叫做T,但是这只是历史上的定名规范,就像调用整数数组索引用i与j一样。模板标识符对整个语句有效,在这个例子中是类模板定义。
        留意:由于历史的原因,可以使用关键字class而不是typename来指定模板类型参数。这样的话,很多册本与程序中的使用语法就酿成了这样:template <class T>。然而,在上下文中使用class关键字会令人迷惑,由于它隐含的意思是类型必须是一个类,现实上不是这样的。类型可以是一个类,一个布局,一个联合,一个原始数据类型,如int或double,等等。为了避免这种迷惑,我们使用typename。
        在前面的GameBoard类中,m_cells数据成员为指针vector,要求特别的代码来拷贝--就须要拷贝构造函数与拷贝赋值操作符。在Grid类中,m_cells是一个可选值的vector,以是编译器生成的拷贝构造函数与赋值操作符就可以了。然而,一旦有了用户声明的析构函数,编译器隐式生成的拷贝构造函数或拷贝赋值操作符就会过时,以是Grid类模板显式地对它们举行了缺省。也显式地缺省了move构造函数与move赋值操作符。下面是显式缺省的拷贝赋值操作符:
  1. Grid& operator=(const Grid& rhs) = default;
复制代码
        可以看到,rhs参数的类型不再是const GameBoard&,而酿成了const Grid&。在类定义内,编译器在须要的地方解释Grid为Grid<T>,但如果你想,可以显式地使用Grid<T>:
  1. Grid<T>& operator=(const Grid<T>& rhs) = default;
复制代码
        然而,在类定义之后,必须使用Grid<T>。当写一个类模板时,过去认为的类名(Grid)现在现实上酿成模板名。当想谈论现实的Grid类或类型时,不得不使用template ID,也就是说,Grid<T>,它是特定类型,好比int,SpreadsheetCell或ChessPiece的Grid类模板的实例化。
        由于m_cells不再保存指针,而酿成了可选值,at()成员函数现在返回optional<T>而不是unique_ptr,也就是说optional可以有一个类型T的值,或者为空:
  1. std::optional<T>& at(std::size_t x, std::size_t y);
  2. const std::optional<T>& at(std::size_t x, std::size_t y) const;
复制代码


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 IT评测·应用市场-qidao123.com技术社区 (https://dis.qidao123.com/) Powered by Discuz! X3.4