std :: pair и деструкторы классов

Возможный дубликат:
Что такое правило трех?

Как именно 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)

(номера строк отключены, потому что я вырезал комментарии и прочее)

Должно быть, я что-то упускаю по поводу _9 _...?

Заранее всем спасибо!


person cmo    schedule 17.01.2012    source источник
comment
Почему бы вам не использовать int array[2] вместо int *array?   -  person Pubby    schedule 17.01.2012
comment
Обратите внимание, что вам не понадобится конструктор копирования или оператор присваивания копии, если вы не распределяете память напрямую. Попробуйте вместо этого std::vector<int> an_array.   -  person Robᵩ    schedule 17.01.2012
comment
@Xeo: во многих случаях лучше использовать стандартные контейнеры и опустить конструктор копирования и присваивание копии. Не думайте слепо, что рукописное копирование - лучшее решение.   -  person Sebastian Mach    schedule 17.01.2012
comment
@phresnel: Эээ, спасибо, я это знаю. Однако, если вам когда-нибудь понадобится поиграть с битами (или реализовать std::vector в качестве домашнего задания), хорошо, хорошо знать о правиле трех.   -  person Xeo    schedule 17.01.2012
comment
@phresnel Следует избегать рукописного копирования конструктора / оператора присваивания, поскольку они могут ограничивать оптимизацию компилятора. Но в этом случае с ручным выделением памяти и использованием STL, который использует внутри себя конструктор копирования / оператор присваивания, вы ДОЛЖНЫ предоставить его. Я не понимаю, почему вы спорите с Xeo, он не говорит, что это лучший дизайн, он говорит, что с этим дизайном вы должны их иметь.   -  person lapk    schedule 17.01.2012
comment
@Xeo: Ваш комментарий подразумевает, что ему нужна явная семантика копирования. Вот почему там мой комментарий. Примечание: Роб придерживается того же мнения.   -  person Sebastian Mach    schedule 17.01.2012
comment
@Azza: Он тебе не нужен. Смотрите мой ответ. Также: в стандарте есть фиксированные правила относительно неявно определенных операций копирования; если вы не слишком сильно отклонитесь от них, если вам придется писать их от руки, я не понимаю, как это могло бы помешать оптимизации.   -  person Sebastian Mach    schedule 17.01.2012
comment
@phresnel См. комментарий к вашему ответу. Оптимизация: считайте, что struct B содержит 2 64-битных int m1,m2. Производные Dcc и Duc: первый использует copy / ass компилятора, второй - определяемый пользователем Duc(Duc const & f):B(f.m1, f.m2){} и тривиальный operator=. Настройка: Dcc lcc(SomeRandomN(), 0), lcc1;, тест: Dcc lcc2(lcc); lcc1=lcc;. То же самое для Duc. В случае Dcc полная оптимизация скорости VC ++ 10: movdqa xmmword ptr [stack.addr],xmm0;, в Duc: mov qword ptr [stack.addr1],rdi; mov qword ptr [stack.addr2],rdi;. Результат: компилятор оптимизирует две инструкции в 1 с копией по умолчанию. На 33% быстрее.   -  person lapk    schedule 17.01.2012
comment
Тем, кто проголосовал против моего вопроса, даже не предлагая какой-либо формы цитизма или объяснения - спасибо за недобросовестность.   -  person cmo    schedule 18.01.2012
comment
@AzzA: Вы пробовали gcc? Например, он векторизует код, который использует std::vector. Или вы видели их реализацию valarray? Он основан на шаблонах выражений. У меня создается впечатление, что вы слишком много внимания уделяете микрооптимизациям, а не ремонтопригодности и правильности. У C ++ есть то преимущество, что по умолчанию вы можете писать хороший и быстрый код. И плохой (в отношении удобства обслуживания), и молниеносный код с некоторыми усилиями.   -  person Sebastian Mach    schedule 18.01.2012


Ответы (4)


Когда вы добавляете my_class в контейнеры stl, вызывается конструктор копирования. Поскольку вы не определяете один, он выполняет поэлементную копию, и создаются два объекта my_class, которые указывают на один и тот же массив int. Когда они удаляются, один и тот же массив int может быть удален дважды

Ознакомьтесь с Правилом трех.

В C ++ 11 также посмотрите конструктор перемещения , если вас беспокоит эффективность.

person parapura rajkumar    schedule 17.01.2012
comment
Спасибо. Я даже не знал, что существуют конструкторы копирования - вот что происходит, когда вы изучаете C ++ только на онлайн-форумах :( - person cmo; 17.01.2012
comment
@CycoMatto Если вы занимаетесь обучением, также поищите в Google move constructor на C ++ 11 - person parapura rajkumar; 17.01.2012
comment
@CycoMatto: Вначале я тоже изучал C ++ через онлайн-ресурсы. Могу вам сказать, что в C ++ это довольно опасно. Если вы не знаете нужных ресурсов, там будет много плохого и вредоносного кода. Прочтите, например, столбец GotW (ссылка в моем ответе). И взгляните на Список. - person Sebastian Mach; 18.01.2012

Ваш класс нарушает правило трех, определяя деструктор без копии конструктор и оператор присваивания. Как только вы их определите, ваш код должен работать нормально: контейнеры STL в значительной степени зависят от них, поэтому вы должны спрашивать себя, реализовали ли вы все три каждый раз, когда вы используете класс в качестве аргумента шаблона для контейнера STL.

person Sergey Kalinichenko    schedule 17.01.2012

Вы должны определить подходящий конструктор копирования, потому что копии вашего класса используют один и тот же массив через скопированные экземпляры указателей.

person AraK    schedule 17.01.2012

Правило трех необычно. Стандартные контейнеры обычно более красивы.


Проблема в том, что копируются не массивы, а указатели на них. Теперь, если два экземпляра содержат одинаковые указатели, вы дважды удалите один и тот же массив.

Вы можете определить правильные операции копирования для своего класса, но обычно использование стандартных контейнеров решает все ваши проблемы, связанные с копированием, получением памяти, освобождением памяти, самоназначением, гарантиями исключений.

  • Используйте std::vector как замену динамическим массивам.
  • Используйте std::array как замену для массивов фиксированного размера.

Если все ваши члены имеют правильную семантику копирования, вашему классу даже не нужны явные операции копирования, поэтому вы сэкономите много работы, повысите ремонтопригодность и уменьшите вероятность ошибок.

So:

Как правило, предпочитайте стандартные контейнеры ручным массивам:

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
}; 

or

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
}; 

Iff вы должны опустить стандартные контейнеры:

  • Напишите конструктор копирования.
  • Напишите задание копии. или
  • Запретить копирование вообще.

Перед тем как это сделать, прочтите правило трех, будь осознавая опасность самостоятельного назначения, знайте трюк с заменой (примечание: это распространенная идиома C ++) и узнайте о безопасность исключений (примечание: вы найдете большую часть содержания книги в серии статей GotW бесплатно).

person Sebastian Mach    schedule 17.01.2012
comment
Ваш ответ ДРАМАТИЧЕСКИ меняет исходный класс. Если вы его не видите, попробуйте распечатать sizeof() вашего класса и исходного класса. На моей машине разница в 28 байт - с исходных 8 до 36 с вашим решением. Учитывая, что автор хочет сохранить там только 2 int, не думаете ли вы, что 28 байтов дополнительных служебных данных для размещения 8 байтов - это БИТ слишком много? Я имею в виду, что размер вашего std::vector<int> сам по себе ДВАЖДЫ больше, чем выделенная память исходного класса PLUS. - person lapk; 17.01.2012
comment
@AzzA Отличный момент. В реальной программе с реальным классом будет несколько тысяч экземпляров класса (и многие переменные-члены не показаны). Так что я полагаю, что об этих дополнительных накладных расходах стоит побеспокоиться ... - person cmo; 17.01.2012
comment
@AzzA: Во-первых: я хотел резко изменить это: безопасность исключений бесплатно, управление памятью бесплатно, копирование семантики бесплатно; видите, кода для управления данными почти нет, но он есть. Когда время / деньги имеют значение, тогда написание десятков тысяч строк правильного кода со всеми шаблонами обходится дорого и чревато ошибками. У ручного управления есть свои варианты использования. Но это в особых случаях, а не в общих. - person Sebastian Mach; 18.01.2012
comment
@Azza: Во-вторых: обычно вы не выделяете массив из двух целых чисел фиксированного размера, поэтому мое предположение, что код CycoMatto был образцовым, не имеет значения. К сожалению, он / она не сообщил подробностей о назначении этого класса, поэтому я предлагаю сначала общее решение, а затем - специальное. - person Sebastian Mach; 18.01.2012
comment
@AzzA: В-третьих: отсутствие знаний о семантике копирования позволяет мне предположить, что CycoMatto является относительно новым для C ++, поэтому предложение ручной памяти и управления копированием без упоминания таких вещей, как самоназначение, трюк с подкачкой и безопасность исключений в целом, довольно вредно для его карьеры . - person Sebastian Mach; 18.01.2012
comment
@AzzA: Наконец: вы упустили мою точку зрения, озаглавленную Яфф, вы должны опустить стандартные контейнеры. - person Sebastian Mach; 18.01.2012