【问题标题】:std::pair and class destructors [duplicate]std::pair 和类析构函数
【发布时间】:2012-02-12 09:52:07
【问题描述】:

可能重复:
What is The Rule of Three?

std::pair 究竟是如何为其组件调用析构函数的?我正在尝试将类的实例添加到 std::map,但我收到有关我的类的析构函数的错误。

我已将我的问题/问题缩小到以下极其简单的示例。

下面,my_class 只是在构造时创建一个 int 数组,并在销毁时将其删除。不知何故,我收到了“双重删除”错误:

//my_class.h
class my_class {
  public:
    int an_int;
    int *array;

    //constructors:
    my_class()
    {
      array = new int[2];
    }
    my_class(int new_int) : an_int(new_int)
    {
      array = new int[2];
    }

    //destructor:
    ~my_class()
    {
      delete[] array;
    }
};  //end of my_class

同时,在 main.cpp...

//main.cpp
int main(int argc, char* argv[])
{
  std::map<int, my_class>   my_map;

  my_map.insert( std::make_pair<int, my_class> (1, my_class(71) ) );

  return 0;
} // end main

编译正常,但这会产生以下运行时错误:

*** glibc detected *** ./experimental_code: double free or corruption (fasttop):

或者,使用 valgrind:

==15258== Invalid free() / delete / delete[] / realloc()
==15258==    at 0x40249D7: operator delete[](void*) (vg_replace_malloc.c:490)
==15258==    by 0x8048B99: main (my_class.h:38)
==15258==  Address 0x42d6028 is 0 bytes inside a block of size 8 free'd
==15258==    at 0x40249D7: operator delete[](void*) (vg_replace_malloc.c:490)
==15258==    by 0x8048B91: main (my_class.h:38)

(行号已关闭,因为我删掉了 cmets 和其他东西)

我一定是错过了std::pair...?

提前感谢大家!

【问题讨论】:

  • 为什么不用int array[2] 而不是int *array
  • 请注意,如果您不直接分配内存,则不需要复制构造函数或复制赋值运算符。请改用std::vector&lt;int&gt; an_array
  • @Xeo:在许多情况下,您可以更好地使用标准容器并省略您的复制构造函数和复制赋值。不要盲目地认为手写复制是最好的解决方案。
  • @phresnel:呃,谢谢,我知道。但是,如果您有一天需要玩这些位(或将std::vector 作为家庭作业),那么了解三法则是件好事。

标签: c++ map destructor std-pair


【解决方案1】:

您必须定义一个合适的复制构造函数,因为您的类的副本通过指针的复制实例共享相同的数组。

【讨论】:

    【解决方案2】:

    当您将my_class 添加到 stl 容器时,将调用复制构造函数。由于您没有定义一个,它会按成员复制并创建两个指向同一个 int 数组的 my_class 对象,当这些对象被删除时,同一个 int 数组可能会被删除两次

    请查看Rule of three

    如果您担心效率,请在 C++11 中查看move constructor

    【讨论】:

    • 谢谢。我什至不知道存在复制构造函数——当你只从在线论坛学习 C++ 时就会发生这种情况:(
    • @CycoMatto 如果你正在疯狂学习,也可以在 C++11 中搜索 move constructor
    • @CycoMatto:一开​​始我也是通过网上资源学习C++的。我可以告诉你,这在 C++ 中是非常危险的。如果您不知道正确的资源,就会有很多不良和有害的代码。例如,阅读 GotW 专栏(我的答案中的链接)。看看The List
    【解决方案3】:

    你的类违反了the rule of three,它定义了一个没有复制构造函数和赋值运算符的析构函数。一旦您定义了这些,您的代码应该可以正常运行:STL 容器严重依赖这些,所以每次使用类作为 STL 容器的模板参数时,您应该问问自己是否实现了所有这三个。

    【讨论】:

      【解决方案4】:

      三法则很花哨。标准容器通常更漂亮。


      问题在于复制的不是数组,而是指向它们的指针。现在,如果两个实例持有相同的指针,您将删除相同的数组两次。

      您可以为您的类定义适当的复制操作,但通常使用标准容器可以解决复制、内存获取、内存释放、自分配、异常保证等所有问题。

      • 使用std::vector 作为动态数组的替代品。
      • 使用std::array 替代固定大小的数组。

      如果您的所有成员都具有正确的复制语义,您的类甚至不需要显式复制操作,因此您可以节省大量工作并提高可维护性并减少错误机会。

      所以:

      总的来说,比手动数组更喜欢标准容器

      class my_class {
      public:
          my_class()
          : new_int(0), array(2)
          {}
      
          my_class(int new_int)
          : an_int(new_int), array(2)
          {}
      
      private:
          int an_int;
          std::vector<int> array; // do not expose them
      }; 
      

      class my_class {
      public:
          my_class()
          : new_int(0)
          {}
      
          my_class(int new_int)
          : an_int(new_int)
          {}
      
      private:
          int an_int;
          std::array<int,2> array; // do not expose them
      }; 
      

      如果你必须省略标准容器:

      • 编写一个复制构造函数。
      • 写副本分配。 或
      • 完全禁止复制。

      在这样做之前,请阅读rule of three,注意self assignment 的危险,了解swap trick(注意:这是一个常见的C++ 习语),并了解exception safety(注意:您'将在 GotW 系列文章中免费找到本书的大量内容)。

      【讨论】:

      • 你的答案极大地改变了原来的课程。如果您没有看到它,请尝试打印您的班级和原始班级的sizeof()。这是我机器上 28 个字节的差异——使用您的解决方案从原来的 8 个字节到 36 个字节。考虑到作者只想保留 2 个int,您不认为 28 个字节的额外开销来容纳 8 个字节有点太多了吗?我的意思是,您的 std::vector&lt;int&gt; 的大小本身比原始类 PLUS 分配的内存大两倍。
      • @AzzA 好点。在真实的程序中,有了真实的类,就会有几千个类的实例(还有很多成员变量没有显示)。所以我想这个额外的开销值得担心......
      • @AzzA:首先:我的意图是彻底改变它:免费的异常安全,免费的内存管理,免费复制语义;看,几乎没有代码来管理数据,但它就在那里。当时间/金钱很重要时,使用所有样板编写数万行正确代码既昂贵又容易出错。手动管理有其用例。但这是在特殊情况下,而不是在一般情况下。
      • @Azza:其次:通常你不会分配一个由两个整数组成的固定大小的数组,所以我认为 CycoMatto 的代码具有示范性的假设并不是题外话。不幸的是,他/她没有详细说明该课程的目的,所以我先提出一般解决方案,然后提出特殊解决方案。
      • @AzzA:第三:缺乏关于复制语义的知识让我假设 CycoMatto 对 C++ 来说相对较新,因此提出手动内存和复制管理,而没有提及自分配、交换技巧和异常安全等内容总的来说对他的职业生涯是相当有害的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-11-04
      • 1970-01-01
      • 2023-03-18
      • 1970-01-01
      • 1970-01-01
      • 2012-03-05
      相关资源
      最近更新 更多