【问题标题】:Deallocating objects stored in a vector?释放存储在向量中的对象?
【发布时间】:2011-04-18 00:47:36
【问题描述】:

我有一个创建对象向量的类。在这个类的解构器中,我试图释放分配给对象的内存。我试图通过遍历向量来做到这一点。所以,如果向量被称为我正在做的地图:

Building::~Building() {
    int i;
    for (i=0; i<maps.size(); i++) {
        delete[] &maps[i];
    }
}

当我运行此程序时,程序会在释放内存时出现段错误。我认为我正在做的实际上是删除存储对象的数组而不是对象本身。它是否正确?如果对我做错了什么没有任何想法?

【问题讨论】:

  • vector 的类型是什么?从外观上看,您的vector 包含对象,而不是指向对象的指针。如果是这种情况,那么您不要删除它们; vector 负责所有的清理工作。 [当然很难说,因为这段代码并不完全是你使用的代码,因为size是成员函数,而不是成员变量;这段代码根本不起作用。]
  • 你的向量声明是什么样子的,更重要的是:你为什么要这样做?
  • 您的意思是 std::vector 还是“vector”作为数组的替代名称?
  • 向我们展示向量声明
  • 由于.size()的返回类型是size_t,最好使用size_t作为索引变量的类型(这里是i)。当然,除非您使用的是 C++0x,否则您可以使用 auto。也更喜欢将“i”的范围限制在尽可能本地,例如仅在 for 循环内。最后选择不同的变量名,因为没有看到声明的人,可能会错误地认为mapsstd::map的复数。

标签: c++ memory-management vector


【解决方案1】:

如果您使用的是std::vector,那么您可以让它由vector 的析构函数处理,假设上述vector 中有“对象”(而不是指向对象的指针)。

-- 或--

如果您使用标准数组作为“vector”:

delete []”变体的目的是解除整个数组的分配,从而避免像您一样需要for 循环。

如果使用标准 C/C++ 数组,“delete [] maps”应该会为您完成。 “[]”不应用于释放 STL vectors。

【讨论】:

  • -1 因为他在这里并没有真正谈论数组。除了不真正适用之外,以这种方式删除 std::vector 确实是一个坏主意。编辑后,仍然为 -1 建议手动调用析构函数 - 这也是一个非常糟糕的主意,因为当对象超出范围时它很可能会再次被调用。如果需要,可以通过swap 清空向量,但即使这样也很少需要。
  • 你说得对,我不是故意建议显式调用析构函数。
【解决方案2】:

这取决于向量是如何定义的。

如果 maps 是 vector&lt;myClass*&gt;,则删除每个元素类似于:

for ( i = 0; i < maps.size(); i++)
{
    delete maps[i];
}

如果 maps 是 vector&lt;myClass&gt; 我认为您不需要删除单个元素。

【讨论】:

  • 这只是故事的一半。您可以按照您所说的分配指针,但仍然做错了。这是我在帖子中所要表达的主旨。当然你是对的,但是理解类型是很容易的部分(他已经弄清楚了一半),因为编译器会告诉你。在我看来,他对内存管理的基础知识有一点误解。
  • 我已经尝试过了,但是在删除 vec[i] 之后,矢量大小仍然相同并且对象在那里。
【解决方案3】:

在 C++ 中,您只能通过指针删除数据。您已经使用 & 运算符完成了此操作,但是如果您的向量不包含指向机器堆上分配的内存的指针(不是堆栈,就像您有普通变量声明时的方法一样),那么您可以尝试删除它,但您会遇到未定义的行为(这可能会导致程序崩溃)。

当您插入向量时,向量会调用类的复制构造函数,您实际上是在插入对象的副本。如果你有一个函数,其唯一目的如下:

无效插入对象(对象和我的对象) { myVector.insert(myObject); }

然后意识到在这个作用域中有两个 obj:你通过引用传入的一个,以及向量中的副本。相反,如果我们通过值而不是通过引用传入 myObject,那么我们可以说该对象的两个副本存在于这个范围内,一个存在于调用者中。在这 3 个实例中,它们不是同一个对象。

如果您将指针存储在容器中,则向量将创建指针的副本(不是对象的副本)并将复制的指针插入向量中。通过指针将元素插入容器通常不是一个好习惯,除非您知道该对象将至少在容器完成使用之前存在。例如,

无效插入() { 对象我的对象; myVector.insert(&myObj); }

这可能是一个非常糟糕的主意,因为你在向量中有一个指针,它指向一个对象,当它超出范围时会自动销毁!

重点是,如果你 malloc'd 或 new'd 你的对象,那么你需要释放或删除它。如果您在堆栈上创建它,则什么也不做。向量被销毁时会处理它。

要更深入地了解基于堆栈的分配与基于堆的分配,请在此处查看我的答案: How does automatic memory allocation actually work in C++?

【讨论】:

    【解决方案4】:

    很难从您使用的术语和代码中确切地看出发生了什么。所以也许一些例子会对你有所帮助。

    数组新建和数组删除

    你问new []delete [] 怎么了?这些家伙用于分配/释放事物数组。这些东西可能是 POD,也可能是成熟的对象。对于对象,它们将在分配后调用构造函数,在解除分配时调用析构函数。

    让我们举一个人为的例子:

    class MrObject
    {
    public:
       MrObject() : myName(new char[9]) { memcpy(myName, "MrObject", 9); }
       virtual ~MrObject() { std::cout << "Goodbye cruel world!\n"; delete [] myName; }
    private:
       char* myName;
    };
    

    现在我们可以用 MrObject 做一些有趣的事情了。

    对象数组

    首先让我们创建一个漂亮而简单的数组:

    MrObject* an_array = new MrObject[5];
    

    这给了我们一个包含 5 个 MrObjects 的数组,所有这些都很好地初始化了。如果我们想删除那个数组,我们应该执行一个数组删除,这反过来又会为每个 MrObject 调用析构函数。让我们试试吧:

    delete [] an_array;
    

    但是如果我们搞砸了,只是进行了正常的删除呢?那么现在是自己尝试的好时机

    delete an_array;
    

    你会看到只有第一个析构函数被调用。那是因为我们没有删除整个数组,只是删除了第一个条目。

    有时候。这里发生的事情真的是不确定的。要点是在使用数组 new 时使用 delete 的数组形式,同样适用于普通的 old new 和 delete。

    对象的向量

    好的,那很有趣。但是现在让我们看一下std::vector。你会发现这个人会为你管理记忆,当他超出范围时,他所持有的一切也会如此。让我们带他出去试一试吧:

    std::vector<MrObject> a_vector(5);
    

    现在你有了一个包含 5 个初始化的 MrObjects 的向量。让我们看看当我们清除那个吸盘后会发生什么:

    a_vector.clear();
    

    你会注意到所有 5 个析构函数都被命中了。

    指向对象的指针向量

    你说的哦,但现在让我们来看看吧。我想要 std::vector 的所有优点,但也想要自己管理所有内存!好吧,也有一条线:

    std::vector<MrObject*> a_vector_of_pointers(5);
    for (size_t idx = 0; idx < 5; idx++) {
       // note: it's just a regular new here, not an arra
       a_vector_of_pointers[idx] = new MrObject;
    }
    

    看到这有点痛苦。但它很有用,您可以在创建 MrObject 时使用非默认构造函数。您可以将派生的 MrObjects 放在那里。好吧,你可以看到天空是极限。可是等等!你创造了那个记忆,你最好地管理它。您需要循环遍历向量中的每个条目并自行清理:

    for (size_t idx = 0; idx < a_vector_of_pointers.size(); idx++) {
       delete a_vector_of_pointers[idx];
    }
    

    【讨论】:

    • @Eric Rahm +1 提供了良好而全面的答案——但我认为这里值得一提的是,在数组为一些派生类,指向数组的指针是基类型。在这里查看答案:stackoverflow.com/a/6171991/1971003
    【解决方案5】:

    从你的问题很难说maps 的签名是什么。我猜你想使用delete[],因为你也使用了new[]。那么这是否意味着您的向量的成员本身就是一个集合?假设它是,那么你有这样的东西:

    class Building {
      public:
        typedef int* maps_t;
      private:
        std::vector<maps_t> maps;
      public:
        Building();
        ~Building();
    };
    
    Building::Building(size_t num_maps) {
      for(;num_maps; --num_maps)
      {
        maps.push_back(new Building::maps_t[10]);  
      }
    }
    

    在这种情况下,你的析构函数几乎是正确的;您只需将&amp;maps[i] 更改为maps[i]

    Building::~Building() {
        int i;
        for (i=0; i<maps.size(); i++) {
            delete[] maps[i];
        }
    }
    

    但在 C++ 中,我们很少喜欢这样做。一方面,除非你真的想实现std::vector 之类的东西,否则你很少想明确地使用new[]delete[]。例如,您可以使用std::vector。在这种情况下,您不需要执行显式内存管理。您的课程将如下所示:

    class Building {
      public:
        typedef std::vector<int> maps_t;
      private:
        std::vector<maps_t> maps;
      public:
        Building();
    };
    
    Building::Building(size_t num_maps) {
      for(;num_maps; --num_maps)
      {
        maps.push_back(Building::maps_t(10));  
      }
    }
    

    在这种情况下没有用户定义的析构函数,因为std::vector 已经很好地管理了自己的内存。

    【讨论】:

    • 可能想提一下,您所指的“很少”是例如。堆栈根本不足以容纳您需要保存的数据。
    【解决方案6】:
    for(std::vector<MyObjectClass>::iterator beg = myVector->begin(), end = myVector->end(); beg != end; beg++)
    {
        delete *beg;
    }
    myVector->clear();
    

    【讨论】:

      【解决方案7】:

      我决定把我的评论变成一个答案(连同这里的其他很好的答案),所以就这样吧。

      我要再次指出,这种情况涉及对象的继承。

      当你删除一个由Base指针指向的Derived对象数组时,如下:

      Base* pArr = new Derived[3];
      
      delete [] pArr;
      

      编译器“在后台”所做的是生成以下代码:

      //destruct the objects in *pArr in the inverse order
      //in which they were constructed
      for (int i = the number of elements in the array - 1; i >= 0; --i)
      {
           pArr[i].Base::~Base(); 
      }
      

      现在,这样做时,我们会得到未定义的行为。处理数组只是处理偏移量,所以当这个循环发生时,在循环的每次迭代中,数组的指针都会根据 Base --> 的大小递增,这就是事情变得“未定义”的地方。 在“简单”(但不太常见)的情况下,派生类不添加任何自己的成员,它的大小与 Base 的大小一样 --> 所以事情可能(我想并非总是如此)运作良好。 但是(!!)当您向 Derived 类添加至少一个成员时,它的大小会增长,导致每次迭代中的偏移增量是错误的。

      为了说明这种情况,我创建了以下 Base 和 Derived 对象。 请注意,在 Derived 不包含 m_c 成员的情况下,删除操作顺利(将其注释掉并自己查看),YET 一旦添加它,我出现分段错误(这是未定义的行为)。

      #include <iostream>
      using namespace std;
      
      class Base 
      {
      
          public:
              Base(int a, int b)
              : m_a(a)
              , m_b(b)    
              {
                 cout << "Base::Base - setting m_a:" << m_a << " m_b:" << m_b << endl;
              }
      
              virtual ~Base()
              {
                  cout << "Base::~Base" << endl;
              }
      
              protected:
                  int m_a;
                  int m_b;
      };
      
      
      class Derived : public Base
      {
          public:
          Derived() 
          : Base(1, 2) , m_c(3)   
          {
      
          }
      
          virtual ~Derived()
          {
              cout << "Derived::Derived" << endl;
          }
      
          private:    
          int m_c;
      };
      
      int main(int argc, char** argv)
      {
          // create an array of Derived object and point them with a Base pointer
          Base* pArr = new Derived [3];
      
          // now go ahead and delete the array using the "usual" delete notation for an array
          delete [] pArr;
      
          return 0;
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-12-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多