【问题标题】:Element Lifetime of STL ContainersSTL 容器的元素生命周期
【发布时间】:2012-03-14 06:04:08
【问题描述】:

我正在尝试将对象保存在 stl 容器(在本例中为矢量)中,并希望容器在其销毁时销毁对象,但我不太清楚细节。

我不想这样做的一种方法是简单地使用它

vector<MyClass> myVec;
myVec.push_back(MyClass(...));

由于这里的构造函数被调用了两次(一次在上面的代码中,然后在向量中复制构造函数)和一次析构函数。

最直接的替代方法是使用指针来存储动态分配的对象,但是在向量销毁时不会调用MyClass 的析构函数。存储 auto_ptr 而不是普通指针会在 myVec.push_back(...) 处出错。

在让容器的析构函数调用元素的析构函数时,有没有办法避免第一个选项?

感谢您的回答!

编辑

考虑类似的问题;如何使用抽象基类实现拥有对象的容器。唯一指针(Boost 的 unique_ptr)没有复制构造函数,所以不能直接使用。

class A {};             // Abstract base class.
class B : public A {};  // Sub class.

...


vector<A *> vec;
vec.push_back(new B());

// At destruction of vec, destroy elements left in container.

【问题讨论】:

  • std::shared_ptr 怎么样?
  • 您愿意使用 C++11 功能或 Boost 吗?
  • 我刚开始使用 Boost (1.49.0),但 auto_ptr 是在 std(内存)中定义的。但是 boost::shared_ptr 可以工作,因为实际所有权应该由容器持有,所以由于引用计数会产生一些开销。
  • 你为什么关心它是否被复制?抄袭很贵吗?
  • 您是否还关心向量内部可能出现的不必要的副本,例如在调整大小或排序时?

标签: c++ memory-management stl


【解决方案1】:

尚未提及的替代方案是Boost Pointer Container Library

Boost.Pointer Container 提供了用于存放堆分配的容器 以异常安全的方式和最小开销的对象。目的 该库的特别是使 C++ 中的 OO 编程更容易 通过建立一套标准的类、方法和设计 处理面向对象的具体问题

使用boost::ptr_vector,您无需推回副本,而是将指针推向动态分配的对象。 ptr_vector 拥有这些对象的所有权,并确保在删除 ptr_vector 本身时删除它们。从ptr_vector 读取的客户端使用与常规std::vector 相同的接口,因此它们不必处理指针。例如,boost::ptr_vector&lt;T&gt;::front() 返回一个引用。

文档的动机部分将帮助您确定这是否是适合您的解决方案。

【讨论】:

    【解决方案2】:

    C++11 有emplace_back,它将完美地将你给它的任何东西转发给元素构造函数并直接就地构造它

    #include <vector>
    #include <iostream>
    
    struct X{
      X(int i, float f, bool b){
        std::cout << "X(" << i << ", " << f << ", " << b << ")\n";
      }
    };
    
    int main(){
      std::vector<X> vx;
      vx.emplace_back(42, 3.14f, true);
    }
    

    Live example on Ideone.

    在 C++03 中,你运气不好,不得不忍受复制 ctor 调用(或者更确切地说,两个 - 一个在参数中,一个在向量的内部数组中)。如果您的课程设计正确,那应该只是轻微的效率不便。

    【讨论】:

    • emplace_back 也(部分)在 C++03 中由 Boost.Container 支持。
    • 当容器拥有对象时,这似乎是避免复制构造的最佳方法。
    • @Mankarse:在 C++03 中没有右值引用怎么可能?
    • @Jesse:注意“部分”(;。Boost.Container 使用 Boost.Move 提供的右值引用模拟。参数总是通过 const 引用或模拟右值转发,所以参数转发不如 C++11 所允许的那么完美。
    【解决方案3】:

    最好的方法是在标准库容器中按值使用元素。
    标准库容器处理值语义。即:它们按值存储元素,并且您确定容器拥有元素的所有权。所以你不需要显式的手动内存管理。

    只要遵守Rule of Three,按值存储元素时调用拷贝构造函数和析构函数对你来说应该不是问题。

    如果指针作为容器元素,您必须手动管理内存并显式取消分配动态分配。
    在这种情况下,您需要将对象驻留在动态内存中,您应该使用 Smart pointers
    auto_ptr 已弃用,并且不能在标准库容器中使用,因为它具有非直觉的赋值行为。 unique_ptr 是新 c++11 标准提出的 auto_ptr 的上等选择。

    请注意,使用哪个智能指针取决于元素的生命周期所有权语义,请查看链接以了解如何为您的使用选择一个。

    【讨论】:

    • 请注意,通常人们希望将指针作为容器成员获得更好的性能,但除非您 profile 并确定 value semantics 是一个问题过早优化也是如此。
    【解决方案4】:

    如果你想存储指针我喜欢boost::ptr_vector

    这类似于向量,但存储并获取指针的所有权。

    boost::ptr_vector&lt;X&gt; 相对于std::vector&lt;some_smart_ptr&lt;X&gt;&gt; 的优势在于ptr_vector 上的元素访问返回对对象的引用,而不是对(智能)指针的引用。这使得使用标准算法的容器更容易(因为您不需要绑定函子来取消引用元素)。

    但除非有很好的理由,否则最好将对象按值存储在普通的std::vector 中。

    充分的理由可能包括:

    • 复制成本很高。
    • 不能使用完美转发就地构建。
    • 容器必须存储多态对象。

    【讨论】:

      【解决方案5】:

      听起来你想要一个值,它的生命周期相当于包含vector&lt;T&gt;。如果是这样,这是一个考虑使用 std::shared_ptr&lt;T&gt; 的好地方,它是一个引用计数的指针类型

      typedef std::shared_ptr<MyClass> MyClassPtr;
      ...
      vector<MyClassPtr> myVec;
      myVec.push_back(new MyClass(...));
      

      【讨论】:

      • 但是 shared_ptr 会延长向量的生命周期。在这种情况下,unique_ptr 可能是更好的智能指针?
      • @juanchopanza 我可以看到两种方式的论点。我认为我们需要 OP 的澄清
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-12-06
      • 2018-02-23
      相关资源
      最近更新 更多