我为一个名为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];
}
现在每当我的代码运行并且调用了复制函数时,我都会收到错误
*** glibc detected *** ./go: double free or corruption (!prev): 0x0982c6a8 ***
之后还有很多其他信息(文字大墙)。那只是意味着内存被删除两次正确吗?如果是这样,怎么会这样?为什么析构函数会被调用两次?
调用它的代码看起来像这样 -
for(;;) {
Grid g;
if(which_display == 1) {
.....
.....
g = myServer->getAgent()->getGrid()->copy(); //HERE
}
//print
std::cout<<g.toString();
}
我觉得我错过了一些明显的东西。有人能指出我如何调用析构函数两次?
答案 0 :(得分:4)
您的复制功能未创建地图的深层副本;它只是复制指针映射包含的。在原始对象和副本上调用析构函数时,这些指针将被删除两次。
答案 1 :(得分:3)
您缺少复制构造函数和赋值运算符。这是Law of the Big Three。
三巨头是:
三巨头的法则是,如果你需要其中一个,那么很有可能你需要这三个。它们通常涉及以非平凡的方式处理资源。
在您的示例中,您在析构函数中显式释放内存。这可能意味着您需要在复制构造函数和赋值运算符中专门处理内存:正确分配新内存并复制值,或阻止复制和赋值(通过将它们声明为私有而不实现它们)。
答案 2 :(得分:2)
您正在从copy
函数返回一个临时对象。你可能想要的是在堆上分配Grid然后传递一个指针(或更好的,一个智能指针):
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的评论中提到的深度复制地图。或者,您也可以在其上使用shared_ptr来节省复制成本。
答案 3 :(得分:1)
您根本不需要copy
方法。您只需要一个复制构造函数和赋值运算符。我猜这条线最初看起来像这样:
g = myServer->getAgent()->getGrid();
由于这不起作用,您添加了复制方法。但是现在的方式,单独修复你的复制方法并不能解决问题,因为你也需要复制构造函数和赋值运算符,或者你在修复复制方法方面的辛苦工作可能会被破坏。
首先,快速解释发生了什么,以及程序失败的原因:
copy
Grid
。Grid
的成员,但我们怀疑它是浅层副本。Grid
的复制构造函数。 * Grid
的析构函数触发,删除map
的内容。Grid
,但指向已删除的内存。Grid
对象已分配到g
。这将调用Grid
的赋值运算符。 * map
内容。热潮。g
超出范围时,其析构函数将尝试再次删除map
的内容。正如您所看到的,有3个地方出现浅拷贝 - 所有都必须修复或者仍然会失败。
如何解决此问题
setMap
和setFilename
以进行深层复制。这是赋值运算符的样子(假设所有set方法都执行深度复制):
Grid& operator= (const Grid& g) {
setFilename(f_name);
setNumOfRows(num_rows);
setNumOfCols(num_cols);
setMap(map);
return *this;
}
有一些技术可以编写复制构造函数,然后使赋值运算符使用复制构造函数。这是一个很好的技术(较少重复的代码),但我没有方便的链接。当我找到它时,我会在这里链接它。
最后,我在解释中标出了几行(*)。编译器可以执行返回值优化(RVO)和命名RVO。通过这些优化,它实际上不会在Grid
内的堆栈上创建copy
对象,然后为返回值复制构造 - 它只会为{{的结果创建临时对象1}},然后copy
方法将使用它而不是它自己的内部基于堆栈的copy
对象。因此,通过足够的编译器优化,您的代码可能会超过此点并在以后崩溃。显然这没有帮助,所以这更像是一个fyi。
答案 4 :(得分:0)
您发布的代码中缺少重要部分,例如类定义和setMap的实现。 Nonetheles,我可以从我看到的内容推断出当编译器调用临时对象的默认复制构造函数(返回值)时可能会发生问题。你最终得到两个Grid对象,它们的map成员指向只分配一次的同一个内存,显然它们各自的析构函数调用都会发生冲突。如果您的班级中有任何动态分配的成员(如地图似乎),您必须执行以下操作之一:
a)完全支持值语义,实现默认和复制构造函数以及赋值运算符 b)通过声明(而不是定义)复制构造函数和赋值运算符私有来禁止它。
如果你的对象要花费很多(在内存和执行时间)来创建,那么选项b)是首选。
如果你去(a),你不需要copy()方法,只需做一个作业。 如果你去(b),你的copy()方法应该返回一个指针(最好是一个智能指针),如dark_charlie所示。在这种情况下,我还建议将copy()重命名为clone(),这是此类方法名称的最常用约定。