【问题标题】:Deleting old dynamically allocated memory in assignment operator overloading method in c++在c ++中的赋值运算符重载方法中删除旧的动态分配的内存
【发布时间】:2015-04-30 16:24:46
【问题描述】:

我有两个相互关联的问题。

我正在实现一个区域四叉树结构。在这个 QTree 类中,我有一些字段,更重要的是一个

vector<QTree*> quads; 

包含当前正方形的四个划分区域的字段。 首先,我想为这个类实现一个析构函数。请注意,我无法在类的公共字段中定义任何内容(如果我是,这简直是小菜一碟!)所以我这样做了:

QTree::~QTree ()
{
  if ( quads [ 0 ] != NULL )
  {
     for ( int i = 0; i < 4; i++ )
     {
        delete ( quads [ i ] );
     }
  } 
}

根据 valgrind,这是可行的。没有内存泄漏和错误,但我不太确定是否如此。您能否就析构函数的有效性发表您的看法?

第二个问题是,在这个类的重载赋值运算符的实现中,如果我在自赋值检查之后和这个方法中的任何其他内容之前写这个:

delete this;

我拿

* ` ' 中的错误:双重释放或损坏(输出):0x00007fffcb4d46d0 * :第 3 行:4227 中止(核心转储)泄漏检查

错误。

如果我写(析构函数中的代码相同。)

if ( quads [ 0 ] != NULL )
    {
        for ( int i = 0; i < 4; i++ )
        {
            delete ( quads [ i ] );
        }
    }

而不是“删除这个;”在重载的赋值运算符中,我没有收到错误。 有什么问题,能解释一下吗?

编辑: 我删除了复制构造函数。

也许问题在于使用对象的自毁。以下是一些有用的信息:https://isocpp.org/wiki/faq/freestore-mgmt#delete-this

【问题讨论】:

  • class QTree 的数据成员是什么?
  • if ( quads [ 0 ] != NULL) 那里有大红旗。
  • @timrau 除了四边形,它们都是堆栈变量。我想,这就是你要问的问题。
  • @self 我不明白你的意思,但让我提一下一个重要问题。 quads 向量被初始化为在构造函数中保存 4 个 NULL 指针。当一个正方形被分割时,这个向量中的指针被动态分配来保存新的 QTree 的正方形的值。

标签: c++ pointers memory-management quadtree


【解决方案1】:

在现代 C++ 中,除非您遇到非常特殊的情况,否则您应该使用 智能指针 类来管理拥有指针,例如 std::unique_ptrstd::shared_ptr

请注意,STL 智能指针可以很好地与 STL 容器结合,从而可以实现一种“确定性(基于引用计数)垃圾收集器”

在你的情况下,我会考虑使用:

// 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++编译器会自动为你实现拷贝构造函数、移动构造函数、拷贝赋值、移动赋值和析构函数。

【讨论】:

    【解决方案2】:
    1. C++ 中的delete 特别允许 0/nullptr 作为参数,所以你的测试是肤浅的
    2. 更安全地将释放循环写入实际向量大小for ( int i = 0; i != quads.size(); i++ )
    3. What is wrong, could you clarify me?。当您调用 delete this 时,您不仅调用了析构函数(使用四边形释放),而且还释放了类的内存,将其取出。如果在堆栈上分配类,您将遇到更多问题。解决方案 - 重构释放并从任何地方调用它

      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();
          ...
      }
      

    【讨论】:

    • 1) 我不明白你的意思。 2)你是对的,但我相信这不是问题。 3)我在上述线程中提供的链接还说,使用堆栈对象时必须避免自毁,但对于您提供的代码,我已经提到我不能在公共字段中声明任何内容,因此您无法在您的 deallocate 方法中访问四边形。
    • @mualloc 的 quads[0] 等于 NULL 可以调用 delete(quads[0])。 op delete() 对 NULL 进行内部检查,这是 c++ 标准所要求的
    • 现在,我明白你在第一个中的意思了。
    • @mualloc 抱歉,错过了取消分配的限定符,已编辑,感谢您的关注。关于delete this,即使它是在堆上分配的,调用也是错误的。您实际上是从对象下方拉出内存。从rhs复制的数据已经没有地方放了
    • @mualloc 是的,我相信你是对的,应该在析构函数和赋值操作中使用相同的释放。这就是为什么我认为最好将其重构为单独的例程并在您必须清除旧资源时调用
    【解决方案3】:

    如果我理解正确,你想要做的是:

    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-&gt;~Qtree() 可以在不释放内存的情况下调用析构函数

    其实也可以使用placement new操作符调用构造函数而不分配内存,所以如果你已经有一个拷贝构造函数,你的赋值操作符可以是:

    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] 数组?

    【讨论】:

      猜你喜欢
      • 2018-08-29
      • 2015-06-25
      • 1970-01-01
      • 1970-01-01
      • 2013-04-18
      • 2012-04-22
      • 2015-06-01
      • 2011-01-27
      • 2011-05-31
      相关资源
      最近更新 更多