文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

玩转C++方法模板,编程技能秒提升

2024-11-30 04:16

关注

警告:虚方法和析构函数不能是方法模板。

考虑仅有一个模板参数的原始 Grid 模板:元素类型。您可以实例化许多不同类型的网格,例如 int 和 double:

Grid myIntGrid;
Grid myDoubleGrid;

然而,Grid 和 Grid 是两种不同的类型。如果你编写一个接受 Grid 类型对象的函数,你不能传递 Grid。即使你知道 int 网格的元素可以复制到 double 网格的元素中,因为 int 可以转换为 double,但你不能将 Grid 类型的对象赋值给 Grid 类型的对象,或从 Grid 构造 Grid。以下两行都无法编译:

myDoubleGrid = myIntGrid; // 无法编译
Grid newDoubleGrid { myIntGrid }; // 无法编译

问题在于 Grid 模板的拷贝构造函数和赋值运算符定义如下:

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

等效于:

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

Grid 的拷贝构造函数和 operator= 都需要一个 const Grid& 的引用。当你实例化 Grid 并尝试调用拷贝构造函数和 operator= 时,编译器生成以下原型的方法:

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

注意,生成的 Grid 类中没有接受 Grid 的构造函数或 operator=。

幸运的是,您可以通过向 Grid 类添加模板化的拷贝构造函数和赋值运算符的版本来纠正这种疏忽,从而生成将一个网格类型转换为另一个网格类型的方法。以下是新的 Grid 类模板定义:

export template 
class Grid {
public:
    template 
    Grid(const Grid& src);

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

    void swap(Grid& other) noexcept;
    // 为了简洁省略部分内容
};

原始的拷贝构造函数和拷贝赋值运算符不能被移除。如果 E 等于 T,编译器不会调用这些新的模板化拷贝构造函数和模板化拷贝赋值运算符。首先查看新的模板化拷贝构造函数:

template 
Grid(const Grid& src);

您可以看到有另一个模板声明,使用不同的类型名 E(代表“元素”)。类在一个类型 T 上进行模板化,新的拷贝构造函数也在不同的类型 E 上进行模板化。这种双重模板化允许您将一个类型的网格复制到另一个类型。以下是新拷贝构造函数的定义:

template 
template 
Grid::Grid(const Grid& src)
    : Grid { src.getWidth(), src.getHeight() } {
    // 此构造函数的 ctor-initializer 首先委托给非拷贝构造函数来分配适当的内存量。
    // 下一步是复制数据。
    for (size_t i { 0 }; i < m_width; i++) {
        for (size_t j { 0 }; j < m_height; j++) {
            m_cells[i][j] = src.at(i, j);
        }
    }
}

如您所见,您必须在成员模板行(带 E 参数)之前声明类模板行(带 T 参数)。您不能像这样组合它们:

template  // 对于嵌套模板构造函数错误!
Grid::Grid(const Grid& src)

除了构造函数定义之前的额外模板参数行外,注意您必须使用公共访问方法 getWidth()、getHeight() 和 at() 来访问 src 的元素。那是因为您正在复制到的对象是 Grid 类型的,而您正在复制的对象是 Grid 类型的。它们不是同一类型,所以您必须使用公共方法。

swap() 方法实现如下:

template 
void Grid::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&,但返回一个 Grid&:

template 
template 
Grid& Grid::operator=(const Grid& rhs) {
    // 使用复制-交换习惯用法
    Grid temp { rhs }; // 在临时实例中完成所有工作。
    swap(temp); // 仅通过非抛出操作提交工作。
    return *this;
}

这个赋值运算符的实现使用了复制-交换习惯用法。swap() 方法只能交换同一类型的 Grids,但这没关系,因为这个模板化赋值运算符首先使用模板化拷贝构造函数将给定的 Grid 转换为 Grid,名为 temp。之后,它使用 swap() 方法将这个临时的 Grid 与 this(也是 Grid 类型)交换。

使用非类型参数的方法模板

不同大小网格的赋值和拷贝

在先前的例子中,使用整数模板参数 HEIGHT 和 WIDTH,主要问题是高度和宽度成为了类型的一部分。这种限制阻止了将一个尺寸的网格赋值给另一个不同尺寸的网格。然而,在某些情况下,将一个大小的网格赋值或拷贝给不同大小的网格是可取的。与其使目标对象成为源对象的完美克隆,不如只从源数组中复制适合目标数组的元素,并在源数组较小的维度上用默认值填充目标数组。使用赋值运算符和拷贝构造函数的方法模板,您可以做到这一点,从而允许赋值和拷贝不同大小的网格。以下是类定义:

export template 
class Grid {
public:
    Grid() = default;
    virtual ~Grid() = default;
    // 明确默认拷贝构造函数和赋值运算符。
    Grid(const Grid& src) = default;
    Grid& operator=(const Grid& rhs) = default;

    template 
    Grid(const Grid& src);

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

    void swap(Grid& other) noexcept;

    std::optional& at(size_t x, size_t y);
    const std::optional& at(size_t x, size_t y) const;

    size_t getHeight() const { return HEIGHT; }
    size_t getWidth() const { return WIDTH; }

private:
    void verifyCoordinate(size_t x, size_t y) const;
    std::optional m_cells[WIDTH][HEIGHT];
};

这个新定义包括拷贝构造函数和赋值运算符的方法模板,以及一个辅助方法 swap()。注意,非模板化的拷贝构造函数和赋值运算符是明确默认的(因为用户声明了析构函数)。它们仅复制或赋值源对象的 m_cells 到目标对象,这正是对于相同大小的两个网格所希望的语义。

下面是模板化拷贝构造函数的实现:

template 
template 
Grid::Grid(const Grid& src) {
    for (size_t i { 0 }; i < WIDTH; i++) {
        for (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();
            }
        }
    }
}

请注意,此拷贝构造函数仅从 src 中复制 x 和 y 维度上的 WIDTH 和 HEIGHT 元素,即使 src 比这更大。如果 src 在任一维度上较小,则额外位置的 std::optional 对象使用 reset() 方法重置。

下面是 swap() 方法和赋值运算符 operator= 的实现:

template 
void Grid::swap(Grid& other) noexcept {
    std::swap(m_cells, other.m_cells);
}

template 
template 
Grid& Grid::operator=(
    const Grid& rhs) {
    // 使用复制-交换习惯用法
    Grid temp { rhs }; // 在临时实例中完成所有工作。
    swap(temp); // 仅通过非抛出操作提交工作。
    return *this;
}

这个赋值运算符的实现使用了复制-交换习惯用法。swap() 方法只能交换相同类型的 Grids,但这是可以的,因为这个模板化赋值运算符首先使用模板化拷贝构造函数将给定的 Grid 转换为 Grid,称为 temp。之后,它使用 swap() 方法交换这个临时 Grid 和 this。

来源:coding日记内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯