【问题标题】:Invalidating deleted pointers?使删除的指针无效?
【发布时间】:2011-10-11 07:59:54
【问题描述】:
template<typename T>
someclass<T>& operator=(const someclass<T>& other)
{
    typename std::vector<T *>::const_iterator rhs;
    typename std::vector<T *>::iterator lhs;

    //identity test
    //this->data is std::vector<T *>

    for(lhs = this->data.begin(); lhs != this->data.end(); lhs++)
    {
        delete *lhs;
    }

    this->data.clear(); // this is what I forgot

    this->data.reserve(other.data.size());
    for (rhs = other.data.begin(); rhs != other.data.end(); rhs++)
    {
        if (NULL == *rhs)
        {
            this->data.push_back(NULL);
        }
        else
        {
            this->data.push_back(new T(**rhs));
        }
    }
}

正如您在 cmets 中看到的,我忘记清除数组中的旧指针。当我第二次调用赋值运算符时,我得到了 glibc 错误,抱怨 double free。提供的唯一信息是已删除的地址。

这让我开始思考如何处理此类已删除指针 - 当您不想再次删除它们时,当您这样做时,这肯定是一个错误。您不能将它们设置为 NULL,因为那时另一个删除将是正确的。您不想保留该值,因为可以将内存位置分配给新创建的对象。

对调试有好处的是一些值,比如 INVALID,你分配给这些指针说“在这个指针上调用 delete 是一个错误”,而不是 NULL,它说“在这个指针上调用 delete 什么都不做”。有这样的吗?

【问题讨论】:

  • 这不是很可靠——你很容易弄错失效代码,错过你试图捕捉的错误。诸如Valgrind 之类的动态分析工具将可靠地捕获双重删除以及许多其他内存错误。 (当然,正如答案所说,避免手动内存管理将首先防止此类错误)
  • Mike Seymour:对,但假设我在删除后添加了*lhs = 0。一切都会好起来的,但我会有内存泄漏,甚至 valgrind 都找不到
  • 是的,如果您故意掩盖错误,那么您将使其更难找到。你为什么要这样做?
  • 这不是故意掩饰。据我所知,删除后将指针设置为 NULL 是一种常见的做法
  • @Dadam:将指针设置为 NULL 可能是一个值得商榷的做法。它会在这里做什么?使容器无限增长。如果用户代码尽职尽责地检查 NULL,结果是您的程序可能运行良好(暂时)但在某种意义上会泄漏内存(内存从未释放并且使用量不断增加)。

标签: c++ pointers memory-management invalidation


【解决方案1】:

没有。当您想要拥有所有权语义时,一个更好的主意是不使用原始指针。如果您将data 的类型设为boost::ptr_vector&lt;T&gt;std::vector&lt;std::unique_ptr&lt;T&gt;&gt;,那么您将不必手动管理指针的生命周期,问题就会消失。

您的容器不能正确支持多态对象,因为您提供的赋值运算符会在将容器中的对象分配给另一个容器时对其进行切片。一个更好的解决方案可能是只拥有一个std::vector&lt;T&gt;。仅当您不依赖指针容器的某些其他属性(例如指向元素的指针不失效或可能更快的排序操作)时,这才是合适的。

【讨论】:

  • 如果指向的对象归std::vector所有,您必须使用某种智能指针。否则,push_back 的异常之类的事情将导致内存泄漏。
  • @James Kanze:我很确定可能不使用智能指针并仍然编写异常安全代码。这只是意味着大量的catch(...) 块和冗余的清理代码。但是,不,这不是一个好主意!
  • 我不能在那里使用 boost,否则我会很乐意使用 unique_ptr 并丢弃析构函数。对于切片:它是一个处理非多态结构的内部类
  • @Mankarse 我怀疑这在理论上是可能的。我不知道在合理的开发时间或容易犯错误的人类程序员的情况下是否可能。我怀疑它需要足够的catch 块,你会致命地错过一个:-)。
【解决方案2】:

解决这个问题的方法是编写不包含任何删除的代码。尽可能使用shared_ptr。当你有一个拥有多态对象的容器时,你也可以使用Pointer Container

【讨论】:

  • 不不不!不要使用shared_ptr。没有理由要为私有数据成员的元素提供共享所有权。使用shared_ptr 会混淆代码中对象的生命周期,并且可能会降低性能。
  • @Mankarse:在 C++03 中,如果您希望对象归标准容器所有,那或多或少是您唯一的选择。 (在 C++11 中,unique_ptr 更可取,因为您给出的原因,但如果没有移动语义,这不是一个选项。)
  • @Mankarse:我还没有到达 C++11 领域 :)
  • @Mike Seymore:没有理由将自己限制在标准容器中。 Boost.Move 可用于创建合理的 unique_ptr(在 C++03 中),然后可以在建议的 Boost.Container 库中的容器中使用。还有Boost.Pointer Container,这可能是最好的解决方案了。
  • 但如果没有这些,shared_ptr 是一个合理的选择。至少代码不会出错。
猜你喜欢
  • 2014-08-22
  • 2023-03-17
  • 2011-08-21
  • 1970-01-01
  • 1970-01-01
  • 2012-07-23
  • 2016-02-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多