在C ++中的赋值运算符重载方法中删除旧的动态分配内存
Deleting old dynamically allocated memory in assignment operator overloading method in c++
我有两个相互关联的问题。
我正在实现区域四叉树结构。在这个 QTree class 中,我有一些字段,更重要的是
vector<QTree*> quads;
包含当前方块的四个分割区域的字段。
首先,我想为这个 class 实现一个析构函数。请注意,我无法在 class 的 public 字段中定义任何内容(如果我是,这就是小菜一碟!)所以我这样做了:
QTree::~QTree ()
{
if ( quads [ 0 ] != NULL )
{
for ( int i = 0; i < 4; i++ )
{
delete ( quads [ i ] );
}
}
}
根据 valgrind 的说法,这有效。没有内存泄漏和错误,但我不太确定是否是。你能谈谈你对析构函数有效性的看法吗?
第二个问题是,在这个 class 的重载赋值运算符的实现中,如果我在自赋值检查之后和这个方法中的任何其他内容之前写这个:
delete this;
我接受
* ` ' 中的错误:双重释放或损坏(输出):0x00007fffcb4d46d0 *
: 第 3 行:4227 中止(核心已转储)泄漏检查
错误。
如果我写(析构函数中的相同代码。)
if ( quads [ 0 ] != NULL )
{
for ( int i = 0; i < 4; i++ )
{
delete ( quads [ i ] );
}
}
而不是 "delete this;" 在重载的赋值运算符中,我没有得到任何错误。
怎么了,能帮我解释一下吗?
编辑:
我删除了复制构造函数。
也许问题出在使用对象的自毁。这里有一些有用的信息:https://isocpp.org/wiki/faq/freestore-mgmt#delete-this
在现代 C++ 中,除非在非常特殊的情况下,否则您应该使用 智能指针 类 来管理拥有指针,例如 std::unique_ptr
或 std::shared_ptr
.
请注意,STL 智能指针可以与 STL 容器很好地结合,从而可以实现一种 "deterministic (reference-count-based) garbage collector".
对于你的情况,我会考虑使用:
// Supports copy semantics
std::vector<std::shared_ptr<QTree>> quads;
或:
// This is movable but not copyable
std::vector<std::unique_ptr<QTree>> quads;
取决于您的需求和设计。
这样就可以享受所谓的"Rule of Zero":C++编译器会自动实现复制构造函数、移动构造函数、复制赋值、移动赋值和析构函数给你。
delete
在 C++ 中特别允许 0/nullptr 作为参数,所以你的测试是肤浅的
- 将释放循环写入实际向量大小更安全
for ( int i = 0; i != quads.size(); i++ )
What is wrong, could you clarify me?
。当您调用 delete this
时,您不仅会调用析构函数(使用四边形重新分配),还会分配 class 的内存,字面意思是将其取出。如果 class 分配在堆栈上,您会遇到更多问题。解决方案 - 重构释放并从任何地方调用它
private void QTree::deallocate() {
for(int i = 0; i != quads.size(); ++i) {
delete (quads[i]);
}
}
QTree::~Qtree() {
deallocate();
...
}
QTree& QTree::operator=(const QTree& rhs) {
deallocate();
...
}
如果我没理解错的话,你想做的是:
QTree& operator =(const QTree& rhs) {
// destroy current content
delete this;
// clone and copy content of rhs to current quads
if (rhs.quads[0] != NULL) {
for (int i = 0; i < 4; ++i) {
quads[i] = new QTree(*rhs.quads[i]);
}
}
return *this;
}
这样你就可以避免在析构函数中重复代码
第一件事:为什么 delete this
没有像你期望的那样工作:那是因为在调用对象析构函数(这是你想要做的)之后,delete this
也会释放内存物体。您自己的当前对象,即您将要用来从另一个对象分配复制值的对象。使用 this->~Qtree()
可以在不释放内存的情况下调用析构函数
事实上,也可以调用构造函数而不使用放置新运算符分配内存,因此如果您已经有一个复制构造函数,您的赋值运算符可以是:
QTree& operator=(const QTree& rhs) {
this->~QTree();
new(this)(rhs);
return *this;
}
看起来很整洁,但不幸的是,不建议这样做,因为它不是异常安全的:如果构造函数抛出异常,您的对象将处于错误状态。
实现赋值运算符的推荐方法之一是使用 nothrow "swap" 方法或函数:
QTree& operator=(const QTree& rhs) {
QTree tmp(rhs);
swap(tmp)
return *this;
}
既然我回答了这个问题,您的代码还有一些其他问题,尤其是使用可变长度向量来始终存储 4 个项目并且始终存储 4 个项目似乎完全没有必要。为什么不只是一个 QTree[4] 数组?
我有两个相互关联的问题。
我正在实现区域四叉树结构。在这个 QTree class 中,我有一些字段,更重要的是
vector<QTree*> quads;
包含当前方块的四个分割区域的字段。 首先,我想为这个 class 实现一个析构函数。请注意,我无法在 class 的 public 字段中定义任何内容(如果我是,这就是小菜一碟!)所以我这样做了:
QTree::~QTree ()
{
if ( quads [ 0 ] != NULL )
{
for ( int i = 0; i < 4; i++ )
{
delete ( quads [ i ] );
}
}
}
根据 valgrind 的说法,这有效。没有内存泄漏和错误,但我不太确定是否是。你能谈谈你对析构函数有效性的看法吗?
第二个问题是,在这个 class 的重载赋值运算符的实现中,如果我在自赋值检查之后和这个方法中的任何其他内容之前写这个:
delete this;
我接受
* ` ' 中的错误:双重释放或损坏(输出):0x00007fffcb4d46d0 * : 第 3 行:4227 中止(核心已转储)泄漏检查
错误。
如果我写(析构函数中的相同代码。)
if ( quads [ 0 ] != NULL )
{
for ( int i = 0; i < 4; i++ )
{
delete ( quads [ i ] );
}
}
而不是 "delete this;" 在重载的赋值运算符中,我没有得到任何错误。 怎么了,能帮我解释一下吗?
编辑: 我删除了复制构造函数。
也许问题出在使用对象的自毁。这里有一些有用的信息:https://isocpp.org/wiki/faq/freestore-mgmt#delete-this
在现代 C++ 中,除非在非常特殊的情况下,否则您应该使用 智能指针 类 来管理拥有指针,例如 std::unique_ptr
或 std::shared_ptr
.
请注意,STL 智能指针可以与 STL 容器很好地结合,从而可以实现一种 "deterministic (reference-count-based) garbage collector".
对于你的情况,我会考虑使用:
// Supports copy semantics
std::vector<std::shared_ptr<QTree>> quads;
或:
// This is movable but not copyable
std::vector<std::unique_ptr<QTree>> quads;
取决于您的需求和设计。
这样就可以享受所谓的"Rule of Zero":C++编译器会自动实现复制构造函数、移动构造函数、复制赋值、移动赋值和析构函数给你。
delete
在 C++ 中特别允许 0/nullptr 作为参数,所以你的测试是肤浅的- 将释放循环写入实际向量大小更安全
for ( int i = 0; i != quads.size(); i++ )
What is wrong, could you clarify me?
。当您调用delete this
时,您不仅会调用析构函数(使用四边形重新分配),还会分配 class 的内存,字面意思是将其取出。如果 class 分配在堆栈上,您会遇到更多问题。解决方案 - 重构释放并从任何地方调用它private void QTree::deallocate() { for(int i = 0; i != quads.size(); ++i) { delete (quads[i]); } } QTree::~Qtree() { deallocate(); ... } QTree& QTree::operator=(const QTree& rhs) { deallocate(); ... }
如果我没理解错的话,你想做的是:
QTree& operator =(const QTree& rhs) {
// destroy current content
delete this;
// clone and copy content of rhs to current quads
if (rhs.quads[0] != NULL) {
for (int i = 0; i < 4; ++i) {
quads[i] = new QTree(*rhs.quads[i]);
}
}
return *this;
}
这样你就可以避免在析构函数中重复代码
第一件事:为什么 delete this
没有像你期望的那样工作:那是因为在调用对象析构函数(这是你想要做的)之后,delete this
也会释放内存物体。您自己的当前对象,即您将要用来从另一个对象分配复制值的对象。使用 this->~Qtree()
事实上,也可以调用构造函数而不使用放置新运算符分配内存,因此如果您已经有一个复制构造函数,您的赋值运算符可以是:
QTree& operator=(const QTree& rhs) {
this->~QTree();
new(this)(rhs);
return *this;
}
看起来很整洁,但不幸的是,不建议这样做,因为它不是异常安全的:如果构造函数抛出异常,您的对象将处于错误状态。
实现赋值运算符的推荐方法之一是使用 nothrow "swap" 方法或函数:
QTree& operator=(const QTree& rhs) {
QTree tmp(rhs);
swap(tmp)
return *this;
}
既然我回答了这个问题,您的代码还有一些其他问题,尤其是使用可变长度向量来始终存储 4 个项目并且始终存储 4 个项目似乎完全没有必要。为什么不只是一个 QTree[4] 数组?