C++深层复制对象

C++ Deep Copy Object

本文关键字:对象 复制 C++      更新时间:2023-10-16

我正在尝试来回深度复制对象。当我运行 gdb 时,在循环迭代后出现以下错误。

Program received signal SIGSEGV, Segmentation fault.
0x0804ab96 in DGCPM::DGCPM (this=0x844b760, cur=0x1) at DGCPM.C:27
27    memcpy(vRCells, cur->vRCells,sizeof(float)*nThetaCells);

我怀疑问题与创建"新类"有关,但我不确定。有什么建议吗?

(注意:"_initialize"代码调用在程序中设置值的 FORTRAN 子例程。

这是运行。C 主文件:

#include "../include/DGCPM.h"
#define particle_num 5
class DGCPM **mallocModels(int n);
int main(int argc, char *argv[]){
  class DGCPM **m;
  class DGCPM **cur;
  m=mallocModels(particle_num);//update
 for(int t = 0; t < 48; t++){
      //Update m, and then...
      cur = m;
      m = (DGCPM**)malloc(sizeof(class DGCPM *)*particle_num);      
      for(int i=0;i<particle_num;i++){
randomidx = ((double)rand() / ((double)RAND_MAX + 1));
    currentidx = find(cumPw,randomidx,particle_num);
        m[i] = new class DGCPM(cur[currentidx]);
    }
      for(int i=0;i<particle_num;i++){
    delete cur[i];
    }
    free(cur);
  }
   return 0;
}

/*============================================================================
   mallocModels - allocate the ensemble of models
  ============================================================================*/
class DGCPM **mallocModels(int n){
  class DGCPM **m;
  m=(class DGCPM **)amjSafeMalloc(sizeof(class DGCPM *)*n,
              (char *)"mallocModels:m");
  for(int i=0;i<n;i++)
    m[i]=new class DGCPM();
  return m;
}

/*============================================================================
  Find - Return a particle index that has a high probability of having a high weight.
  ============================================================================*/
int find(float *cumPw, double randomidx, int nM){
    /*Wrong implementation*/
        int index = 0;
        flag = 0;
    while(flag == 0){
        if(cumPw[i] >= randomidx){
        flag = 1;
        i++;
    }
        else{
        index ++;
        }
        }
    return index; //Sometimes, index was going to number of models, or number of models + 1, which are out of bounds.
/*Correct implementation*/
    int index = 0;
    for(int i = 0; i < nM-1; i++){
    if(cumPw[i] >= randomidx){
        index = i;
    break;
    }
    }   
    if(index >= nM){
    index = nM-1;
    printf("Error: random index exceeds bounds");
    }
    return index;
}

下面是 DGCPM.h 头文件:

class DGCPM{
public:
  DGCPM(); /* Initialized with defaults setup */
  DGCPM(class DGCPM *cur); //Copy constructor
  DGCPM(int nThetaCells, int nPhiCells, float thetaMin, float thetaMax); 
  ~DGCPM(); /* Free memory */
private:
  int internal; /* 1=memory allocated internally and should be deallocated when ~DGCPM is called, 2=memory is internal except for mGridN which is external */
  int nThetaCells,nRCells,nPhiCells;
  float thetaMin,thetaMax;
  float rMin,rMax;
  float delR,delPhi;
  float deltMax;
  float *vRCells; /* [nThetaCells] */
  float *vThetaCells; /* [nThetaCells] */
  float *vPhiCells; /* [nPhiCells] */
  float **mGridB; /* [nPhiCells][nThetaCells] */
  float **mGridBi; /* [nPhiCells][nThetaCells] */
  float **mGridPot; /* [nPhiCells][nThetaCells] */
  float **mGridEr; /* [nPhiCells][nThetaCells] */
  float **mGridEp; /* [nPhiCells][nThetaCells] */
  float **mGridVr; /* [nPhiCells][nThetaCells] */
  float **mGridVp; /* [nPhiCells][nThetaCells] */
  float **mGridN; /* [nPhiCells][nThetaCells] */
  float **mGridHalf; /* [nPhiCells][nThetaCells] Particles / weber (workspace for upwind and superbee) */
  float **mGridDen; /* [nPhiCells][nThetaCells] */
  float **mGridVol; /* [nPhiCells][nThetaCells] */
  float **mGridX; /* [nPhiCells][nThetaCells] */
  float **mGridY; /* [nPhiCells][nThetaCells] */
  float **mGridOc; /* [nPhiCells][nThetaCells] */
  float **std; /* [nPhiCells][nThetaCells] */
  float parI[2]; 
  float delTMax;
  float Re;
  void initialize(int nThetaCells, int nPhiCells, float thetaMin, 
          float thetaMax);
};

最后是DGCPM。C 对象包装器:

/******************************************************************************
 * DGCPM.C - This implements the DGCPM plasmasphere model class               *
 ******************************************************************************/
#define TWO_PI 6.2831853071795864769252866
#include "../include/DGCPM.h"
# include <cstdlib>
# include <cmath>
/*============================================================================
  DGCPM::DGCPM()
  Initialize with default setup
  ============================================================================*/
DGCPM::DGCPM(){
  internal=1;
  initialize(200,200,14.963217,60.0);/*(180,200,14.963217,60.0);*/
}
//Copy Constructor
DGCPM::DGCPM(class DGCPM *cur){
  internal=1;
  initialize(200,200,14.963217,60.0);/*(180,200,14.963217,60.0);*/
  memcpy(vRCells, cur->vRCells,sizeof(float)*nThetaCells);
  memcpy(vPhiCells, cur->vPhiCells,sizeof(float)*nPhiCells);
  memcpy(vThetaCells, cur->vThetaCells,sizeof(float)*nThetaCells);
  memcpy(mGridB[0], cur->mGridB[0],sizeof(float)*nThetaCells*nPhiCells);
  memcpy(mGridBi[0], cur->mGridBi[0],sizeof(float)*nThetaCells*nPhiCells);
  memcpy(mGridPot[0], cur->mGridPot[0],sizeof(float)*nThetaCells*nPhiCells);
  memcpy(mGridEr[0], cur->mGridEr[0],sizeof(float)*nThetaCells*nPhiCells);
  memcpy(mGridEp[0], cur->mGridEp[0],sizeof(float)*nThetaCells*nPhiCells);
  memcpy(mGridVr[0], cur->mGridVr[0],sizeof(float)*nThetaCells*nPhiCells);
  memcpy(mGridVp[0], cur->mGridVp[0],sizeof(float)*nThetaCells*nPhiCells);
  memcpy(mGridN[0], cur->mGridN[0],sizeof(float)*nThetaCells*nPhiCells);
  memcpy(mGridHalf[0], cur->mGridHalf[0],sizeof(float)*nThetaCells*nPhiCells);
  memcpy(mGridDen[0], cur->mGridDen[0],sizeof(float)*nThetaCells*nPhiCells);
  memcpy(mGridVol[0], cur->mGridVol[0],sizeof(float)*nThetaCells*nPhiCells);
  memcpy(mGridOc[0], cur->mGridOc[0],sizeof(float)*nThetaCells*nPhiCells);
  memcpy(mGridX[0], cur->mGridX[0],sizeof(float)*nThetaCells*nPhiCells); 
  memcpy(mGridY[0], cur->mGridY[0],sizeof(float)*nThetaCells*nPhiCells);
  memcpy(std[0], cur->std[0],sizeof(float)*nThetaCells*nPhiCells);
} 

/*============================================================================
  DGCPM::~DGCPM()
  Free allocated memory
  ============================================================================*/
DGCPM::~DGCPM(){
  if(internal>=1){
    amjFree1dFloat(vRCells);
    amjFree1dFloat(vThetaCells);
    amjFree1dFloat(vPhiCells);
    amjFree2dFloat(mGridB);
    amjFree2dFloat(mGridBi);
    amjFree2dFloat(mGridEr);
    amjFree2dFloat(mGridEp);
    amjFree2dFloat(mGridVr);
    amjFree2dFloat(mGridVp);
    if(internal==1) amjFree2dFloat(mGridN);
    amjFree2dFloat(mGridHalf);
    amjFree2dFloat(mGridDen);
    amjFree2dFloat(mGridVol);
    amjFree2dFloat(mGridX);
    amjFree2dFloat(mGridY);
    amjFree2dFloat(mGridOc);
    amjFree2dFloat(std);
  }
}

/******************************************************************************
 ******************************************************************************
 **                         Private functions                                **
 ******************************************************************************
 ******************************************************************************/
/*============================================================================
  DGCPM::initialize(int nThetaCells, int nPhiCells, float thetaMin, 
          float thetaMax);
  This is the initialization function used when all memory should be
  allocated internally.
  ============================================================================*/
void DGCPM::initialize(int nThetaCells, int nPhiCells, float thetaMin, 
               float thetaMax){
  initialize(nThetaCells,nPhiCells,thetaMin,thetaMax,
         amjMalloc1dFloat(nThetaCells,(char *)"DGCPM::DGCPM:vRCells"),
         amjMalloc1dFloat(nThetaCells,(char *)"DGCPM::DGCPM:vThetaCells"),
         amjMalloc1dFloat(nPhiCells,(char *)"DGCPM::DGCPM:vPhiCells"),
         amjMalloc2dFloat(nPhiCells,nThetaCells,
                  (char *)"DGCPM::DGCPM:mGridB"),
         amjMalloc2dFloat(nPhiCells,nThetaCells,
                  (char *)"DGCPM::DGCPM:mGridBi"),
         amjMalloc2dFloat(nPhiCells,nThetaCells,
                  (char *)"DGCPM::DGCPM:mGridPot"),
         amjMalloc2dFloat(nPhiCells,nThetaCells,
                  (char *)"DGCPM::DGCPM:mGridEr"),
         amjMalloc2dFloat(nPhiCells,nThetaCells,
                  (char *)"DGCPM::DGCPM:mGridEp"),
         amjMalloc2dFloat(nPhiCells,nThetaCells,
                  (char *)"DGCPM::DGCPM:mGridVr"),
         amjMalloc2dFloat(nPhiCells,nThetaCells,
                  (char *)"DGCPM::DGCPM:mGridVp"),
         amjMalloc2dFloat(nPhiCells,nThetaCells,
                  (char *)"DGCPM::DGCPM:mGridN"),
         amjMalloc2dFloat(nPhiCells,nThetaCells,
                  (char *)"DGCPM::DGCPM:mGridHalf"),
         amjMalloc2dFloat(nPhiCells,nThetaCells,
                  (char *)"DGCPM::DGCPM:mGridDen"),
         amjMalloc2dFloat(nPhiCells,nThetaCells,
                  (char *)"DGCPM::DGCPM:mGridVol"),
         amjMalloc2dFloat(nPhiCells,nThetaCells,
                  (char *)"DGCPM::DGCPM:mGridX"),
         amjMalloc2dFloat(nPhiCells,nThetaCells,
                  (char *)"DGCPM::DGCPM:mGridY"),
         amjMalloc2dFloat(nPhiCells,nThetaCells,
                  (char *)"DGCPM::DGCPM:mGridOc"),
         //Added by J.Wise
         amjMalloc2dFloat(nPhiCells,nThetaCells,
                  (char *)"DGCPM::DGCPM:std"));
}
/*============================================================================
  DGCPM::initialize(int nThetaCells, int nPhiCells, float thetaMin, 
          float thetaMax);
  This is the initialization function used when mGridN is passed from
  the outside but all other memory is allocated internally.
  ============================================================================*/
void DGCPM::initialize(int nThetaCells, int nPhiCells, float thetaMin, 
               float thetaMax, float **mGridN){
  initialize(nThetaCells,nPhiCells,thetaMin,thetaMax,
         amjMalloc1dFloat(nThetaCells,(char *)"DGCPM::DGCPM:vRCells"),
         amjMalloc1dFloat(nThetaCells,(char *)"DGCPM::DGCPM:vThetaCells"),
         amjMalloc1dFloat(nPhiCells,(char *)"DGCPM::DGCPM:vPhiCells"),
         amjMalloc2dFloat(nPhiCells,nThetaCells,
                  (char *)"DGCPM::DGCPM:mGridB"),
         amjMalloc2dFloat(nPhiCells,nThetaCells,
                  (char *)"DGCPM::DGCPM:mGridBi"),
         amjMalloc2dFloat(nPhiCells,nThetaCells,
                  (char *)"DGCPM::DGCPM:mGridPot"),
         amjMalloc2dFloat(nPhiCells,nThetaCells,
                  (char *)"DGCPM::DGCPM:mGridEr"),
         amjMalloc2dFloat(nPhiCells,nThetaCells,
                  (char *)"DGCPM::DGCPM:mGridEp"),
         amjMalloc2dFloat(nPhiCells,nThetaCells,
                  (char *)"DGCPM::DGCPM:mGridVr"),
         amjMalloc2dFloat(nPhiCells,nThetaCells,
                  (char *)"DGCPM::DGCPM:mGridVp"),
         mGridN,
         amjMalloc2dFloat(nPhiCells,nThetaCells,
                  (char *)"DGCPM::DGCPM:mGridHalf"),
         amjMalloc2dFloat(nPhiCells,nThetaCells,
                  (char *)"DGCPM::DGCPM:mGridDen"),
         amjMalloc2dFloat(nPhiCells,nThetaCells,
                  (char *)"DGCPM::DGCPM:mGridVol"),
         amjMalloc2dFloat(nPhiCells,nThetaCells,
                  (char *)"DGCPM::DGCPM:mGridX"),
         amjMalloc2dFloat(nPhiCells,nThetaCells,
                  (char *)"DGCPM::DGCPM:mGridY"),
         amjMalloc2dFloat(nPhiCells,nThetaCells,
                  (char *)"DGCPM::DGCPM:mGridOc"),
         amjMalloc2dFloat(nPhiCells,nThetaCells,
                  (char *)"DGCPM::DGCPM:std"));
}
    /*
      initialize() - this initialization function uses pre-allocated
      memory areas passed in from the outside. This function is used both
      when DGCPM allocates memory itself and when it receives
      pre-allocated memory from the outside in order to eliminate
      duplication of code with the associated risk of errors. 
      ============================================================================*/
    void DGCPM::initialize(int nThetaCells, int nPhiCells, float thetaMin, 
                   float thetaMax, float *vRCells, float *vThetaCells,
                   float *vPhiCells, float **mGridB, float **mGridBi, 
                   float **mGridPot, float **mGridEr, float **mGridEp,
                   float **mGridVr, float **mGridVp, float **mGridN, 
                   float **mGridHalf, float **mGridDen, float **mGridVol,
                   float **mGridX, float **mGridY, float **mGridOc, float **std){
      DGCPM::nThetaCells=nThetaCells;
      DGCPM::nPhiCells=nPhiCells;
      DGCPM::thetaMin=thetaMin;
      DGCPM::thetaMax=thetaMax;
      DGCPM::vRCells=vRCells;
      DGCPM::vThetaCells=vThetaCells;
      DGCPM::vPhiCells=vPhiCells;
      DGCPM::mGridB=mGridB;
      DGCPM::mGridBi=mGridBi;
      DGCPM::mGridPot=mGridPot;
      DGCPM::mGridEr=mGridEr;
      DGCPM::mGridEp=mGridEp;
      DGCPM::mGridVr=mGridVr;
      DGCPM::mGridVp=mGridVp;
      DGCPM::mGridN=mGridN;
      DGCPM::mGridHalf=mGridHalf;
      DGCPM::mGridDen=mGridDen;
      DGCPM::mGridVol=mGridVol;
      DGCPM::mGridX=mGridX;
      DGCPM::mGridY=mGridY;
      DGCPM::mGridOc=mGridOc;
      DGCPM::std=std;
      Re=6.378e6;

      initialize_(&nThetaCells,&nRCells,&nPhiCells,&thetaMin,&thetaMax,&rMin,&rMax,
              &delR,&delPhi,vRCells,vThetaCells,vPhiCells,mGridB[0],mGridBi[0],
              mGridN[0],mGridDen[0],mGridVol[0],mGridX[0],mGridY[0],mGridOc[0],std[0]);
    }

下面是一个示例自定义内存函数,它负责初始化和分配:

void *amjSafeMalloc(int n, char *message){
  void *d;
  d=malloc(n);
  if(d==NULL){
    fprintf(stderr,"amjSafeMalloc error: Could not allocate %d bytes "
        "for %s. Exiting.n",n,message);
    exit(1);
  }
  return d;
} 
float *amjMalloc1dFloat(int a, char *message){
  float *d;
  sprintf(msg,"%s:amjMalloc1DFloat:d",message);
  d=(float *)amjSafeMalloc(sizeof(float)*a,msg);
  return d;
}
float **amjMalloc2dFloat(int a, int b, char *message){
  float **d;
  int i;
  sprintf(msg,"%s:amjMalloc2DFloat:d",message);
  d=(float **)amjSafeMalloc(sizeof(float *)*a,msg);
  sprintf(msg,"%s:amjMalloc2DFloat:d[0]",message);
  d[0]=(float *)amjSafeMalloc(sizeof(float)*a*b,msg);
  for(i=1;i<a;i++) d[i]=d[i-1]+b;
  return d;
}
class DGCPM
{
public:
    DGCPM(int nThetaCells, int nPhiCells)
    : nThetaCells(nThetaCells)
    , nPhiCells(nPhiCells)
    , mGridB(nThetaCells, vector<float>(nPhiCells)) // first Y then X
    {
    }
private:
    int nThetaCells, nPhiCells;
    vector<vector<float>> mGridB;
};

免费提供深度拷贝。免费删除内存。

免费的意思是你不必编写代码。

从您的评论/* [nPhiCells][nThetaCells] */您的类定义中,我认为您打算将float**作为 2D 数组。但是,如果可以像 2D 数组一样使用它们,它们实际上是指向数组的指针数组。这是一个巨大的区别:这意味着,你必须复制nPhiCells nThetaCells元素的单个数组,并且你必须设置指针数组本身。现在,当你这样做时

memcpy(mGridHalf[0], cur->mGridHalf[0],sizeof(float)*nThetaCells*nPhiCells);

复制构造函数中,假定没有指针数组,并且所有线数组在内存中都是顺序的。要么此副本超出指针数组的边界(段错误(,要么通过mGridHalf[i][j]访问数组只是做了错误的事情,将float数据重新解释为指针(和段错误(。


不幸的是,C++是一种与 fortran 多维数组交互的可怕语言,因为它没有可变大小数组的概念。所以下面是C代码,而不是C++代码。在 C 中,您可以像这样解决该问题:

float (*mGridHalf)[nThetaCells] = malloc(nPhiCells*sizeof(*mGridHalf));

将正确分配和键入一个 2D 数组(即数组数组(,

可以使用
mGridHalf[phi][theta] = 7.3;

由于所有元素在内存中都是连续的,因此可以正确复制整个元素

memcpy(mGridHalf, cur->mGridHalf, nPhiCells*sizeof(*mGridHalf));

并释放

free(mGridHalf);

从技术上讲,mGridHalf现在是指向数组的指针,数组访问调用的指针算术有效地执行与编写相同的计算

float* foo = malloc(nPhiCells*nThetaCells*sizeof(*foo));
foo[phi*nThetaCells + theta] = 7.3;

但是,使用正确的指针类型 float (*)[nThetaCells] 可以避免自己执行索引计算。

问题很可能是你假设 float** 的数据是一个连续的内存块。 如果是这样,这是实现此目的的一种方法。 首先,我展示了错误的方式(但经常使用(:

float** createFloat2D(int nRows, int nCols)
{
    float** p1 = new float*[nRows];
    for (int i = 0; i < nCols; ++i )
       p1[i] =  new float[nCols];
    return p1;
}
void destroyFloat2D(float**f, int nRows, int nCols)
{
   for (int i = 0; i < nCols; ++i )
       delete [] f[i];
   delete [] f;
}

看起来很简单,并且适用于大多数目的,但如果假设数据位于连续的内存块中,则会失败。

创建 2D 数组的另一种方法是使数据连续。

float** createFloat2D(int nRows, int nCols)
{
    float** p1 = new float*[nRows];  // allocate row pointers
    float* p2 = new float[nRows * nCols];  // allocate data in one chunk
    for (int i = 0; i < nCols; ++i, p2 += nCols )
       p1[i] = p2;  // point the row pointers into the pool of memory
    return p1;
}
void destroyFloat2D(float**f)
{
   delete [] f[0];
   delete [] f;
}

请注意,数据是在一个连续的"池"中创建的。 现在,使用yourArray[0]实际上指向这个记忆的开始。 另请注意,销毁是在不必知道行数或列数的情况下完成的,因为 f[0] 指向内存池。

所以现在,这样的代码应该可以工作了

float** mGridB = createFloat2D(nThetaCells, nPhiCells);
//...
memcpy(mGridB[0], cur->mGridB[0], sizeof(float)*nThetaCells*nPhiCells);

如果我们使用创建 2d 数组的第二种方法,上面的代码现在可以正常工作。

我仍然会坚持使用 1-D 浮点数组的向量,因为您有指向数据的指针(请参阅我之前的评论(。 对于上面的代码,我会将其包装在一个可以轻松处理创建和销毁的类中。

最后一件事是复制构造函数。 C++ 中的复制构造函数具有以下可能的签名:

DGCPM(const DGCPM&);
DGCPM(DGCPM&);
DGCPM(volatile DBCPM&);

我可能错过了一个,但签名应该是上面的签名之一,很可能是第一个(您也可以在引用参数之后有其他参数,但它们都必须具有默认值(。

请注意,正如代码所述,DBCPM*不是复制构造函数的有效参数 - 请记住,复制构造函数不仅供使用,而且编译器也将使用它来制作副本。 因此,要向编译器发出"是的,此函数用于制作副本"的信号,您的函数必须与上面的签名之一匹配。

此外,你需要一个赋值运算符,换句话说,类需要实现"3 法则"。

这听起来很愚蠢(基本编程错误(:我的索引"i"超出了(模型数量 - 1(,所以我在访问不存在的内存时遇到了分段错误。