【问题标题】:How do stl containers get deleted?stl容器如何被删除?
【发布时间】:2010-09-27 20:03:09
【问题描述】:

stl中的vector之类的容器对象如何被销毁,即使它们是在堆中创建的?

编辑

如果容器持有指针,那么如何销毁那些指针对象

【问题讨论】:

    标签: c++ memory-management stl vector containers


    【解决方案1】:

    与堆中的任何其他对象一样,它必须手动销毁(使用删除)。

    【讨论】:

      【解决方案2】:

      当向量超出范围时,编译器会调用其析构函数,从而释放堆上分配的内存。

      【讨论】:

        【解决方案3】:

        这有点用词不当。与大多数 STL 容器一样,向量由 2 个逻辑部分组成。

        1. 向量实例
        2. 实际的底层数组实现

        虽然可配置,但#2 几乎总是位于堆上。 #1 但是可以存在于堆栈或堆上,这取决于它是如何分配的。比如

        void foo() { 
          vector<int> v;
          v.push_back(42);
        }
        

        在这种情况下,第 1 部分位于堆栈中。

        现在#2 是如何被破坏的?当向量的第一部分被破坏时,它也会破坏第二部分。这是通过删除向量类的析构函数内的底层数组来完成的。

        【讨论】:

          【解决方案4】:

          回答你的第一个问题:

          STL 类没有什么特别之处(我希望如此)。它们的功能与其他模板类完全相同。因此,如果在堆上分配它们,它们不会自动销毁,因为 C++ 对它们没有垃圾收集(除非你用一些花哨的 autoptr 业务或其他东西告诉它)。如果您在堆栈上分配它(没有 new),它很可能会由 C++ 自动管理。

          对于第二个问题,这里有一个非常简单的 ArrayOfTen 类来演示 C++ 中典型内存管理的基础知识:

          /* Holds ten Objects. */
          class ArrayOfTen {
              public:
                  ArrayOfTen() {
                      m_data = new Object[10];
                  }
          
                  ~ArrayOfTen() {
                      delete[] m_data;
                  }
          
                  Object &operator[](int index) {
                      /* TODO Range checking */
                      return m_data[index];
                  }
          
              private:
                  Object *m_data;
          
                  ArrayOfTen &operator=(const ArrayOfTen &) { }
          };
          
          ArrayOfTen myArray;
          myArray[0] = Object("hello world"); // bleh
          

          基本上,ArrayOfTen 类在堆上保留了一个由十个 Object 元素组成的内部数组。当在构造函数中调用 new[] 时,堆上分配了十个对象的空间,并构造了十个对象。同理,在析构函数中调用delete[]时,十个对象被解构,然后释放之前分配的内存。

          对于大多数(所有?)STL 类型,调整大小是在幕后完成的,以确保留出足够的内存来容纳您的元素。上面的类只支持十个对象的数组。它基本上是 Object[10] 的一个非常有限的 typedef。

          【讨论】:

            【解决方案5】:

            指针的 STL 容器不会清除指向的数据。它只会清理保存指针的空间。如果你想让向量清理指针数据,你需要使用某种智能指针实现:

            {
                std::vector<SomeClass*> v1;
                v1.push_back(new SomeClass());
            
                std::vector<boost::shared_ptr<SomeClass> > v2;
                boost::shared_ptr<SomeClass> obj(new SomeClass);
                v2.push_back(obj);
            }
            

            当该作用域结束时,两个向量都将释放它们的内部数组。 v1 将泄漏创建的 SomeClass,因为数组中只有指向它的指针。 v2 不会泄露任何数据。

            【讨论】:

              【解决方案6】:

              如果您有一个vector&lt;T*&gt;,您的代码需要在删除向量之前删除这些指针:否则,该内存会泄漏。

              知道 C++ 不做垃圾回收,这里有一个例子说明原因(语法错误的道歉,我已经有一段时间没有写 C++了):

              typedef vector<T*> vt;
              ⋮
              vt *vt1 = new vt, *vt2 = new vt;
              T* t = new T;
              vt1.push_back(t);
              vt2.push_back(t);
              ⋮
              delete vt1;
              

              最后一行(delete vt1;)显然不应该删除它包含的指针;毕竟,它也在vt2中。所以它没有。 vt2的删除也不会。

              (如果你想要一个在销毁时删除指针的向量类型,当然可以编写这样的类型。可能已经写过了。但要小心删除其他人仍然持有副本的指针。)

              【讨论】:

              • 是的,它有:boost::ptr_vector。 (参见 boost ptr 容器)
              【解决方案7】:

              如果将指针存储在 STL 容器类中,则需要在对象被销毁之前手动删除它们。这可以通过遍历整个容器并删除每个项目来完成,或者通过使用某种智能指针类来完成。但是不要使用 auto_ptr,因为它根本不适用于容器。

              这样做的一个好的副作用是您可以在程序中保留多个指针容器,但这些对象只属于其中一个容器,您只需要清理那个容器。

              删除指针最简单的方法是:

              for (ContainerType::iterator it(container.begin()); it != container.end(); ++it)
              {
                  delete (*it);
              }
              

              【讨论】:

              • 这是不可取的(除非是析构函数的一部分),因为它不是异常安全的。你应该使用一个可以理解的容器,它持有一个指针。
              • 如何不异常安全?例如,如果 ContainerType 类似于 std::vector<:string> 那么这是完全有效的代码,可以同时放入析构函数和“清除”或“重置”之类的方法。
              【解决方案8】:

              在向量内使用智能指针,或使用 boost 的 ptr_vector。它将自动释放其中分配的对象。还有地图、套路等。

              http://www.boost.org/doc/libs/1_37_0/libs/ptr_container/doc/ptr_vector.html 和主站点: http://www.boost.org/doc/libs/1_37_0/libs/ptr_container/doc/ptr_container.html

              【讨论】:

                【解决方案9】:

                为了删除指向的元素,我写了一个简单的函子:

                template<typename T>
                struct Delete {
                    void operator()( T* p ) const { delete p; }
                };
                
                std::vector< MyType > v;
                // ....
                std::for_each( v.begin(), v.end(), Delete<MyType>() );
                

                但是当向量的内容要...共享时,您应该使用共享指针。是的。

                【讨论】:

                  【解决方案10】:
                  【解决方案11】:

                  标准 STL 容器使用复制构造函数将原始对象的副本放入容器中。当容器被销毁时,容器中每个对象的析构函数也会被调用以安全地销毁该对象。

                  指针的处理方式相同。
                  问题是指针是 POD 数据。指针的复制构造函数只是复制地址,POD 数据没有析构函数。如果您希望容器管理指针,您需要:

                  • 使用智能指针容器。 (例如共享指针)。
                  • 使用 boost ptr 容器。

                  我更喜欢指针容器:
                  指针容器与 STL 容器相同,只是您将指针放入其中,但容器随后会获得指针指向的对象的所有权,因此会在容器被销毁时释放对象(通常通过调用 delete)。

                  当您访问 ptr 容器的成员时,它们会通过引用返回,因此它们的行为就像在标准算法中使用的标准容器一样。

                  int main()
                  {
                      boost::ptr_vector<int>    data;
                  
                      data.push_back(new int(5));
                      data.push_back(new int(6));
                  
                      std::cout << data[0] << "\n";  // Prints 5.
                      std::cout << data[1] << "\n";  // Prints 6.
                  
                  
                  }   // data deallocated.
                      // This will also de-allocate all pointers that it contains.
                      // by calling delete on the pointers. Therefore this will not leak.
                  

                  还应该指出,容器中的智能指针是一种有效的替代方案,不幸的是 std::auto_ptr 在这种情况下不是智能指针的有效选择。

                  这是因为 STL 容器假定它们包含的对象是可复制的,不幸的是 std::auto_ptr 在传统意义上是不可复制的,因为它破坏了复制时的原始值,因此无法复制的来源常量。

                  【讨论】:

                    【解决方案12】:

                    STL 容器与任何其他对象一样,如果您实例化一个,它将在堆栈上创建:

                    std::vector<int> vec(10);
                    

                    就像任何其他堆栈变量一样,它只存在于定义它的函数的范围内,不需要手动删除。 STL容器的析构函数会调用容器中所有元素的析构函数。

                    在容器中保存指针是一个冒险的问题。由于指针没有析构函数,我会说你永远不想将原始指针放入 STL 容器中。以异常安全的方式执行此操作将非常困难,您必须在代码中添加 try{}finally{} 块,以确保始终释放包含的指针。

                    那么你应该将什么放入容器而不是原始指针? +1 jmucchiello 用于提出 boost::shared_ptr。 boost::shared_ptr 在 STL 容器中使用是安全的(与 std::auto_ptr 不同)。它使用简单的引用计数机制,并且可以安全地用于不包含循环的数据结构。

                    对于包含循环的数据结构,您需要什么?在这种情况下,您可能想学习垃圾收集,这本质上意味着使用不同的语言,如 Java。但这是另一个讨论。 ;)

                    【讨论】:

                      猜你喜欢
                      • 2023-02-10
                      • 1970-01-01
                      • 2020-07-01
                      • 2013-04-07
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2010-11-05
                      • 1970-01-01
                      相关资源
                      最近更新 更多