【问题标题】:Destructor called on object when adding it to std::list将对象添加到 std::list 时调用对象的析构函数
【发布时间】:2009-02-05 08:46:59
【问题描述】:

我有一个 Foo 对象和一个 std::list 保存它的实例。我的问题是,当我向列表中添加一个新实例时,它首先调用 ctor,然后调用 dtor。然后是另一个实例上的 dtor(根据 this 指针)。

单个实例被添加到列表中,但由于调用了它的 dtor(连同它的父级),因此无法按预期使用该对象。

这里有一些简化的代码来说明问题:

#include <iostream>
#include <list>

class Foo
{
public:
    Foo()
    { 
        int breakpoint = 0;
    }
    ~Foo()
    { 
        int breakpoint = 0;
    }
};

int main()
{
    std::list<Foo> li;
    li.push_back(Foo());
}

【问题讨论】:

    标签: c++ constructor stdlist


    【解决方案1】:

    当您 push_back() Foo 对象时,该对象被复制到列表的内部数据结构中,因此会调用另一个实例的 Dtor 和 Ctor。

    C++ 中的所有标准 STL 容器类型都按值获取它们的项,因此根据需要复制它们。例如,每当向量需要增长时,向量中的所有值都可能被复制。

    也许您想在列表中存储 指针 而不是对象。通过这样做,只有指针而不是对象被复制。但是,通过这样做,您必须确保在完成后删除对象:

    for (std::list<Foo*>::iterator it = list.begin(); it != list.end(); ++it) {
        delete *it;
    }
    list.clear();
    

    或者,您可以尝试使用某种“智能指针”类,例如来自 Boost 库。

    【讨论】:

    • 啊!这解释了很多,所以这就是为什么实例不起作用,因为它们没有定义正确的复制构造函数?看起来指针是我最好的选择。
    • mizipzor:你应该在编写一个类时决定它是否应该有一个复制ctor和operator=。如果不是,您应该为这些声明私有原型。如果您的代码尝试使用复制 ctor 或 operator=,您将收到错误消息。
    • Boost 指针容器库 (boost.org/doc/libs/1_37_0/libs/ptr_container/doc/…) 在这里值得一提。例如, boost::ptr_vector 比“等效的” std::vector<:shared_ptr> > 更有效,并提供更好的接口
    【解决方案2】:

    您正在这里创建一个临时 Foo:

    li.push_back( Foo() )
    

    push_back 将该 Foo 复制到其内部数据结构中。临时 Foo 在 push_back 执行后被销毁,调用析构函数。

    您将需要一个适当的复制构造函数来增加您不想过早销毁的类成员的一些引用计数 - 或者将其设为私有以强制自己使用指针解决方案。

    【讨论】:

      【解决方案3】:

      用这个对象来理解:

      class Foo
      {
      public:
          Foo(int x): m_x(x)
          { 
          std::cout << "Constructed Object: " << m_x << ")\n";
          }
          Foo(Foo const& c): m_x(c.m_x+100)
          {
          std::cout << "Copied Object: " << m_x << ")\n";
          }
          ~Foo()
          {  
          std::cout << "Destroyed Object: " << m_x << ")\n";
          }
      };
      

      第一主线

      std::list<Foo*> li;
      li.push_back(Foo(1));
      

      这里我们创建一个临时的 Foo 对象并调用 push_back()。临时对象被复制到列表中并且函数返回。在此语句完成后,临时对象将被销毁(通过析构函数)。当列表被销毁时,它也会销毁它包含的所有对象(Foo 是一个带有析构函数的对象,因此销毁包括调用析构函数)。

      所以你应该看到这样的东西:

      Constructed Object: 1
      Constructed Object: 101
      DestroyedObject: 1
      DestroyedObject: 101
      

      在第二个例子中你有:

      std::list<Foo*> li;
      li.push_back(new Foo(1));
      

      在这里,您可以在堆上动态创建一个对象。然后调用 push_back()。这里指针被复制到列表中(指针没有构造函数/析构函数),所以没有其他任何事情发生。该列表现在包含指向堆上对象的指针。当函数返回时,什么都不做。当列表被销毁时,它会销毁(注意destroy和delete之间的细微差别)它包含的对象(指针)但指针没有析构函数,所以不会发生任何事情你会泄漏内存。

      所以你应该看到这样的东西:

      Constructed Object: 1
      

      【讨论】:

        【解决方案4】:

        这里实际发生的是您将传递的对象的副本存储在列表中,因为您是通过值而不是通过引用发送它。所以第一个被调用的 dtor 实际上是在你传递给 push_back 方法的对象上调用的,但是此时已经创建了一个新实例,现在它存储在列表中。

        如果您不想创建 Foo 对象的副本,请将指向 Foo 对象的指针存储在列表中,而不是对象本身。当然,这样做时,您必须在销毁列表时正确释放内存。

        【讨论】:

          【解决方案5】:

          使列表保存指针而不是实例可以解决调用析构函数的问题。但我还是想知道为什么会这样。

          #include <iostream>
          #include <list>
          
          class Foo
          {
          public:
              Foo()
              { 
                  int breakpoint = 0;
              }
              ~Foo()
              { 
                  int breakpoint = 0;
              }
          };
          
          int main()
          {
              std::list<Foo*> li;
              li.push_back(new Foo());
          }
          

          【讨论】:

          • 因为你从不删除对象,所以实际上从不调用析构函数并且你有内存泄漏
          猜你喜欢
          • 2017-03-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-01-13
          • 2014-11-23
          • 2012-03-15
          • 2019-04-22
          • 1970-01-01
          相关资源
          最近更新 更多