【问题标题】:2D mesh class implementation in C++C++ 中的二维网格类实现
【发布时间】:2020-10-23 21:10:43
【问题描述】:

我想实现一个内存高效的 2D 网格类,因为它将用于 Monte Carlo 模拟。然而,在这个阶段,这实际上与二维数组类相同。不用说,我是 C++ 新手。我写了以下代码。

#define MESH2D_H_INCLUDED

class Mesh2D
{
private:

    double* mesh;
    size_t  rows;
    size_t  columns;

public:

    /* constructor */
    Mesh2D(size_t Nx, size_t Ny){
        rows    = Nx;
        columns = Ny;
        mesh    = new double[Nx*Ny] {};
        std::cout << "Mesh created." << std::endl;
    }

    /* destructor */
    ~Mesh2D()
    {
        delete[] mesh;
        std::cout << "Mesh deleted." << std::endl;
    }

    /* accessors */
    double getrows() const {return rows;}
    double getcolumns() const {return columns;}
    double& operator()(size_t i, size_t j)
    {
        if (i > rows || j > columns){
            throw std::out_of_range("Index exceeds array bounds.");
        }
        return mesh[j + i*columns]; // row major access
    }

    /* copy constructor */
    Mesh2D& operator=(const Mesh2D& rhs) // what is the difference between this line and the following? inline operator=(const Mesh2D& rhs)
    {
        if (rhs.rows != rows || rhs.columns != columns){
            throw std::out_of_range("Assignment cannot be performed, mesh dimensions do not agree.");
        }
        mesh = new double [rows*columns];

//        I want to avoid using a for loop
//        for (int i=0; i<rows*columns; i++){
//            mesh[i] = rhs.mesh[i];
//        }
//      Use this instead
        memcpy(mesh, rhs.mesh, sizeof(rhs)); //however the output is not the expected.
        std::cout << "copied!" << std::endl;
    }

};

#endif // MESH2D_H_INCLUDED
//MAIN FUNCTION

#include <iostream>
#include "Mesh2D.h"

void PrintMesh2D(Mesh2D &mesh){ //why isn't it going to work if I add const? I mean: void PrintMesh2D(const Mesh2D &mesh)

    for (int i=0; i<mesh.getrows(); i++){

        for (int j=0; j<mesh.getcolumns(); j++){

            std::cout << mesh(i,j) << " ";
        }
        std::cout << std::endl;
    }
}

int main()
{

    Mesh2D mesh{3,3};
    Mesh2D newmesh{3,3};

    for (int i=0; i<mesh.getrows(); i++){
        for (int j=0; j<mesh.getcolumns(); j++){
            mesh(i,j) = j + i * mesh.getcolumns();
        }
    }

    newmesh = mesh;
    PrintMesh2D(newmesh);

}

我的问题是用 cmets 写的,但我也在这里列出:

  1. 在复制构造函数内部,Mesh2D&amp; operator=(const Mesh2D&amp; rhs)这一行和inline operator=(const Mesh2D&amp; rhs)这一行有什么区别?

  2. 再次,在复制构造函数中,我想避免使用 for 循环并改用 memcpy。但是,输出不是预期的,我做错了什么?

  3. 在 main 函数内部:为什么这不会编译? void PrintMesh2D(const Mesh2D &amp;mesh)

  4. 最后,实现完成了吗?我的意思是,是否缺少任何重要的特性/功能?

欢迎您给我任何建议。由于我是 C++ 新手,我感觉自己在编写错误的错误代码。

编辑:

我写了以下作为复制构造函数。

/* copy constructor */
    Mesh2D (const Mesh2D& rhs)
    {
        rows    = rhs.rows;
        columns = rhs.columns;
        mesh    = new double[rows*columns];
        memcpy(mesh,rhs.mesh,rows*columns*sizeof(double));
        std::cout << "Copied!" << std::endl;
    } 

看起来不错?

【问题讨论】:

  • 在复制构造函数内部, -- 你的Mesh 类没有复制构造函数。它有一个赋值运算符,因此它缺少Mesh 可安全复制的必要函数 -- 由于我是 C++ 新手,我觉得我正在编写错误的错误代码。 - - 如果您使用std::vector&lt;double&gt; mesh; 而不是mesh *,您可以缓解所有这些问题。
  • 您的测试代码不是一个好的测试。这个简单的 2 行程序会导致问题:int main() { Mesh2D mesh{3,3}; Mesh2D newmesh = mesh; } -- 这表明问题是因为缺少复制构造函数。
  • 您应该阅读rule of 3。您的赋值运算符泄漏内存——事实上,您在赋值运算符中的所有代码都应该在复制构造函数中。除此之外,赋值运算符(和复制构造函数)根本不应该检查任何网格条件——它应该复制发送给它的任何内容,无论好坏。否则,您会得到假副本作为合法副本传递,从而导致一些非常难以发现的错误发生。
  • 所以我只写了这个:``` /* 复制构造函数 /Mesh2D (const Mesh2D& rhs) { rows = rhs.rows;列 = rhs.columns;网格=新双[行列]; memcpy(mesh,rhs.mesh,rowscolumnssizeof(double)); std::cout
  • 您需要将其添加到您的原始帖子中,而不是在评论中。

标签: c++ arrays class multidimensional-array mesh


【解决方案1】:

我会尝试将缺少的部分添加到您的代码中:

  1. 复制构造函数

  2. 一个工作赋值运算符。

在下面的代码之后,我会评论:

#include <iostream>
#include <cstring>
#include <algorithm>

class Mesh2D
{
    private:
    size_t  rows;
    size_t  columns;
    double* mesh;

public:

    Mesh2D(size_t Nx, size_t Ny) : rows(Nx), columns(Ny), mesh(new double[Nx*Ny]{})
    {
        std::cout << "Mesh created." << std::endl;
    }

    Mesh2D(const Mesh2D& rhs) : rows(rhs.rows), columns(rhs.columns), 
                                mesh(new double[rhs.rows * rhs.columns])
    {
       memcpy(mesh, rhs.mesh, (rhs.rows * rhs.columns) * sizeof(double));

       // or better yet
       // std::copy(rhs.mesh, rhs.mesh + rhs.rows * rhs.columns, mesh);
    }
    
    Mesh2D& operator=(const Mesh2D& rhs)
    {
       if ( &rhs != this )
       {
          Mesh2D temp(rhs);
          std::swap(temp.rows, rows);
          std::swap(temp.columns, columns);
          std::swap(temp.mesh, mesh);
      }
      return *this;
    }
       
    ~Mesh2D()
    {
        delete[] mesh;
        std::cout << "Mesh deleted." << std::endl;
    }


    double getrows() const {return rows;}
    double getcolumns() const {return columns;}

    double& operator()(size_t i, size_t j)
    {
        if (i > rows || j > columns){
            throw std::out_of_range("Index exceeds array bounds.");
        }
        return mesh[j + i*columns]; // row major access
    }

    double& operator()(size_t i, size_t j) const
    {  
        if (i > rows || j > columns){
            throw std::out_of_range("Index exceeds array bounds.");
        }
        return mesh[j + i*columns]; // row major access
    }
};

首先,注意使用成员初始化列表来初始化Mesh2D类的成员。这样做是一个好习惯,而不是在构造函数的主体内分配。


其次,注意赋值运算符。它使用copy / swap idiom 安全干净地释放旧内存并进行复制。


第三,operator() 应该有一个const 重载,否则你永远不能在这样的情况下使用你的类:

void foo(const Mesh2D& m)
{
   std::cout << m(0,0);
}

原因是mconst 引用,因此您只能在其上调用const 函数。因此,operator() 需要 const 版本以及非常量版本。

另外,operator() 的非 const 版本也需要存在的原因是您可能想要这样做:

void foo2(Mesh2D& m)
{
   m(0,0) = 23;
}

如果m只有const版本的operator(),由于operator()改变了对象的内部状态,无法进行上述操作。


另一个问题是您错误地使用了memcpymemcpy 适用于字节数,而不是数组中的项目数。因此,您需要乘以sizeof(double),因为这是您要复制的字节数。更安全的解决方案是使用std::copy,它会根据项目数自动使用正确的复制功能。


最后,对错误“Mesh2D”的测试已从赋值运算符中删除。原因是赋值运算符(和复制构造函数)的工作是一回事,而且只有一件事——复制。

当您开始将业务逻辑引入到复制分配函数中时,您就有可能制作不是副本而是被传入对象的伪副本。

这会导致 C++ 中一些最讨厌和最难发现的错误,即当您的复制分配函数想要玩游戏而不是真正复制时。如果您的复制构造函数或赋值运算符根据传入的值做出太多决策并执行逻辑,您就会知道什么时候做错了。


为了完成,这里是相同版本的类,但使用std::vector&lt;double&gt;。请注意代码要小得多,因为不需要用户定义的赋值运算符、复制构造函数或析构函数:

#include <iostream>
#include <vector>

class Mesh2D
{
    private:
    size_t  rows;
    size_t  columns;
    std::vector<double> mesh;

public:

    Mesh2D(size_t Nx, size_t Ny) : rows(Nx), columns(Ny), mesh(Nx*Ny)
    {
        std::cout << "Mesh created." << std::endl;
    }

    double getrows() const {return rows;}
    double getcolumns() const {return columns;}

    double& operator()(size_t i, size_t j)
    {
        if (i > rows || j > columns){
            throw std::out_of_range("Index exceeds array bounds.");
        }
        return mesh[j + i*columns]; 
    }

    const double& operator()(size_t i, size_t j) const
    {  
        if (i > rows || j > columns)    {
            throw std::out_of_range("Index exceeds array bounds.");
        }
        return mesh[j + i*columns]; 
    }
};

【讨论】:

  • 为什么下面的测试程序会崩溃? ``` Mesh2D 网格{3,3}; Mesh2D 新网格;新网格=网格; PrintMesh2D(newmesh);``` 请注意,我没有使用复制赋值运算符。如果我使用它,我会在控制台输出中获得垃圾值。很抱歉在评论中再次编写代码。真的不知道如何发布这个问题以便您可以看到它。
  • 什么崩溃?该程序甚至无法编译,因为您试图创建一个不带参数的Mesh2DHere is a full example
  • 我忘了说我写了一个构造函数 Mesh2D() : rows(0), columns(0), mesh(nullptr)
  • 我直接在上面复制并粘贴了代码,并将垃圾值作为输出。如果我注释掉复制赋值运算符,我不明白为什么程序会崩溃。另外,为什么我需要两个 () 重载?
【解决方案2】:
  1. inline operator=(const Mesh2D&amp; rhs) 是语法错误,它错过了返回类型。另外Mesh2D&amp; operator=(const Mesh2D&amp; rhs) 是复制赋值运算符,而不是复制构造函数,即Mesh2D(const Mesh2D&amp; rhs)
  2. 您可以使用memcpy,但最后一个参数应该是正确的大小rows*colums*sizeof(double)sizeof(rhs) 是类对象的大小,与数组大小无关。由于您在复制赋值运算符中而不是在复制构造函数中,mesh 的内存已经分配,​​因此您应该从复制赋值运算符中删除 mesh = new double [rows*columns];。您还必须在那里执行return *this; 以匹配返回类型。
  3. 因为你需要const版本的仿函数:double operator()(size_t i, size_t j) const
  4. 阅读规则 3 或规则 5,如果您有构造函数和析构函数,则应实现复制构造函数。
  5. 来自@PaulMcKenzie:使用 std::vector 不必执行 1、2 或 4。

这是一个完整的例子:https://ideone.com/seOSwN

【讨论】:

  • 您错过了 5)。使用 std::vector&lt;double&gt; 不必执行 1、2 或 4。
  • @mch 但是inline operator=(const Mesh2D&amp; rhs) 编译正常
  • @PaulMcKenzie 我补充说。我想这可能是一个练习,下一个任务是使用std::vector 来了解它有多容易。
  • @FragiadoulakisAggelos 那是因为你忘记了赋值运算符末尾的return 语句。
  • @mch 您可以将分配运算符中不必要的检查添加到列表中以查找“坏网格”。赋值运算符有一个目标,也只有一个目标——删除现有内存并进行复制。如果Mesh2D 不好,运气不好,无论如何都会制作副本。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-17
  • 2015-11-14
  • 2022-11-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多