2

可能的重复:
什么是三法则?

究竟如何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)

(行号关闭,因为我删掉了评论和东西)

我一定错过了一些关于std::pair...的东西?

提前感谢大家!

4

4 回答 4

10

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

请看三法则

如果您担心效率,请在 C++11 中查看移动构造函数。

于 2012-01-17T15:31:19.730 回答
7

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

于 2012-01-17T15:31:52.727 回答
4

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

于 2012-01-17T15:30:40.460 回答
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
}; 

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

  • 编写一个复制构造函数。
  • 写副本作业。或者
  • 完全禁止复制。

在这样做之前,请阅读规则三,注意自赋值的危险,了解交换技巧(注意:这是一个常见的 C++ 习惯用法),并了解异常安全(注意:你会发现很多本书的内容在 GotW 系列文章中免费)。

于 2012-01-17T15:33:47.790 回答