C++ 双自由误差

C++ 双自由误差,c++,c,memory,free,dealloc,C++,C,Memory,Free,Dealloc,我为一个名为copy的对象创建了一个函数,该函数应该只返回一个具有所有相同值的对象实例- Grid Grid::copy() { Grid result; result.setFilename(f_name); result.setNumOfRows(num_rows); result.setNumOfCols(num_cols); result.setMap(map); return result; } 我的析构函数看起来像这样- Grid::~Grid() { for(int r=

我为一个名为copy的对象创建了一个函数,该函数应该只返回一个具有所有相同值的对象实例-

Grid Grid::copy() {

Grid result;

result.setFilename(f_name);
result.setNumOfRows(num_rows);
result.setNumOfCols(num_cols);
result.setMap(map);


return result;
}
我的析构函数看起来像这样-

Grid::~Grid() {
for(int r=0;r<num_rows;r++)
    delete [] map[r];
}  
for(;;) {
    Grid g;

    if(which_display == 1) {

       .....
       .....
        g = myServer->getAgent()->getGrid()->copy(); //HERE


    }
    //print
    std::cout<<g.toString();
}
还有很多其他的信息,在那之后就是一堵巨大的文字墙。这就意味着内存被删除了两次,对吗?如果是,这怎么可能?为什么析构函数会被调用两次

调用它的代码如下所示-

Grid::~Grid() {
for(int r=0;r<num_rows;r++)
    delete [] map[r];
}  
for(;;) {
    Grid g;

    if(which_display == 1) {

       .....
       .....
        g = myServer->getAgent()->getGrid()->copy(); //HERE


    }
    //print
    std::cout<<g.toString();
}

我觉得我错过了一些明显的东西。有人能告诉我析构函数是如何被调用两次的吗?

您正在从复制函数返回一个临时对象。您可能希望在堆上分配网格,然后传递指针或更好的智能指针:

Grid *Grid::copy() {

Grid *result = new Grid();

result->setFilename(f_name);
result->setNumOfRows(num_rows);
result->setNumOfCols(num_cols);
result->setMap(map);


return result;
}
智能指针版本您还可以将std::shared_ptr与C++11一起使用:

boost::shared_ptr<Grid> Grid::copy() {

boost::shared_ptr<Grid> result(new Grid());

result->setFilename(f_name);
result->setNumOfRows(num_rows);
result->setNumOfCols(num_cols);
result->setMap(map);


return result;
}
在您发布的代码中,当函数退出时,结果将被销毁,并且您将获得未定义的行为


编辑:还要确保深度复制Chad评论中提到的地图。或者,您也可以使用共享的ptr来节省复制成本。

您的复制功能不是创建地图的深度副本;它只是复制映射包含的指针。当对原始对象和副本调用析构函数时,这些指针将被删除两次。

您缺少一个副本构造函数和赋值运算符。这是最重要的

三巨头是:

复制构造函数 赋值运算符 析构函数 三巨头的法则是,如果你需要其中一个,你很有可能需要所有三个。它们通常涉及以非平凡的方式处理资源


在您的示例中,显式释放析构函数中的内存。这可能意味着您需要在复制构造函数和赋值运算符中专门处理内存:要么正确分配新内存并复制值,要么通过声明它们为私有而不实现它们来阻止复制和赋值。

您发布的代码中缺少重要部分,比如类定义和setMap的实现。不过,我可以从我看到的情况推断,当编译器调用临时对象的默认复制构造函数返回值时,问题可能会发生。最终会有两个网格对象,它们的映射成员指向只分配了一次的同一内存,显然它们各自的析构函数调用将发生冲突。如果您的类中有任何动态分配的成员,比如map,您必须执行以下操作之一:

完全支持值语义,实现默认和复制构造函数以及赋值运算符 b通过声明而不是定义复制构造函数和赋值运算符private来禁止它

如果创建对象的内存和执行时间都很昂贵,那么选项b是首选选项

如果你选择了复制,你不需要复制方法,只需要做一个赋值。 如果选择b,复制方法应该返回指针,最好是智能指针,如dark_charlie所示。在这种情况下,我还建议将copy重命名为clone,这是此类方法名称最常用的约定。

您实际上根本不需要copy方法。您只需要一个复制构造函数和赋值运算符。我猜你的台词最初是这样的:

g = myServer->getAgent()->getGrid();
由于这不起作用,您添加了复制方法。但现在的情况是,单独修复复制方法无法解决问题,因为您还需要复制构造函数和赋值运算符,否则修复复制方法的辛苦工作可能会被破坏

首先,快速解释发生了什么以及程序失败的原因:

你叫复制 这将进入复制方法,该方法在堆栈上创建网格。 它设置网格的成员,但我们怀疑它是浅层复制。 copy方法返回,它调用网格的copy构造函数* 默认复制构造函数执行浅层复制。 基于堆栈的网格的析构函数触发,删除映射的内容。 copy方法现在已经返回,提供了一个临时网格,但它指向已删除的内存。 现在,临时栅格对象被指定到g中。这将调用网格的赋值操作符* 默认赋值运算符执行浅复制,就像默认复制构造函数那样。 在这行的末尾,临时对象被销毁,它试图删除map中已经被删除的内容。繁荣 当g超出范围时,其析构函数将再次尝试删除map的内容。 正如您所看到的,有3个地方会发生浅复制-所有这些都必须修复,否则仍然会失败

如何解决这个问题

摆脱复制方法-它无论如何都不提供任何值。 修复setMap和setFilename以进行深度复制。 创建赋值运算符。这将深度复制另一个网格的内容。 创建一个复制构造函数,就像赋值操作符一样。 下面是赋值运算符的内容 可能看起来像是假设所有集合方法都执行深度复制:

Grid& operator= (const Grid& g) {
  setFilename(f_name);
  setNumOfRows(num_rows);
  setNumOfCols(num_cols);
  setMap(map);

  return *this;
}
有一些技术可以编写复制构造函数,然后让赋值运算符使用复制构造函数。这是一个很好的减少重复代码的技术,但是我手头没有链接。当我找到它时,我会把它链接到这里


最后,我在解释中标出了几行*。编译器可以对RVO进行返回值优化,并命名为RVO。通过这些优化,它实际上不会在copy中的堆栈上创建网格对象,然后为返回值创建copy构造-它只会为copy的结果创建临时对象,然后copy方法将使用该对象而不是它自己的基于堆栈的内部网格对象。因此,有了足够的编译器优化,您的代码可能会超过这一点,并在以后崩溃。显然,这没有什么帮助,所以这只是供参考。

需要更多的代码才能确定,但似乎您的setMap调用正在对它所持有的一些内存进行浅拷贝。在您的result.setMapmap中,它会对映射进行深拷贝吗?如果不是,那就是你的问题。换句话说,Chad说的C++ 1。我需要看看你的StMMAP FN。什么是MAP,为什么不使用java资源管理智能指针?通常不需要在C++中编写复制方法——只需编写复制构造函数,然后在实例上使用赋值运算符,例如GRAPG= *MyServer > GETActudio> GETGRID;这并不能解决问题。它只是将其延长到调用delete result@工具箱:是的,编辑了我的答案以提及代码中的另一个bug返回对象有什么问题?为什么返回指针,不管是智能的还是其他的,都更好?