2016-06-27 5 views
-2

мне нужно добавить объекты одного и того же класса к вектору:C++ добавление объектов в вектор разрушает ранее объекты

#include <vector> 
#include <cstdio> 

class A { 
    int *array; 
    int size; 
public: 
    A(int s) { 
     array = new int[size = s]; 
     fprintf(stderr, "Allocated %p\n", (void*)array); 
    } 
    ~A()  { 
     fprintf(stderr, "Deleting %p\n", (void*)array); 
     delete array; 
    } 
}; 

int main() { 
    std::vector<A> v; 

    for (int n = 0; n < 10; n++) { 
     fprintf(stderr, "Adding object %d\n", n); 
     v.push_back(A(10 * n)); 
     //v.emplace_back(10 * n); 
    } 
    return 0; 
} 

Когда я запускаю эту программу, он выходит из строя после того, как производить следующий вывод:

Adding object 0 
Allocated 0x146f010 
Deleting 0x146f010 
Adding object 1 
Allocated 0x146f050 
Deleting 0x146f010 
*** glibc detected *** ./a.out: double free or corruption (fasttop): 0x000000000146f010 *** 

Кажется, что деструктор 0-го объекта вызывается при добавлении 1-го объекта. Даже незнакомец, когда я использую emplace_back вместо push_back:

Adding object 0 
Allocated 0x1644030 
Adding object 1 
Allocated 0x1644080 
Deleting 0x1644030 
Adding object 2 
Allocated 0x1644100 
Deleting 0x1644030 
Deleting 0x1644080 
Adding object 3 
Allocated 0x1644160 
Adding object 4 
Allocated 0x1644270 
Deleting 0x1644030 
*** glibc detected *** ./a.out: double free or corruption (fasttop): 0x0000000001644030 *** 

Может кто-нибудь объяснить, почему это происходит, и правильный способ сделать это? Компилятор использовали г ++ 4.7.2 под Linux, но я также получаю такое же поведение с лязгом 7.3.0 под Mac OS X.

+2

«Вектор» копирует/перемещает существующие элементы, когда ему необходимо распределить выделенное хранилище, а ваш класс не следует [Правило три] (http://stackoverflow.com/q/4172722/241631). – Praetorian

+1

Зачем использовать динамический массив? Используйте вектор в своем классе A. – DeathTails

+0

Ничего себе, все это довольно странные предположения. Также этот код одинаково странный. –

ответ

3

Ваш A класс не следует Rule of Three:

правило трех (также известное как Закон Большой тройки или Большая тройка) является эмпирическим правилом на C++ (до C++ 11), утверждающим, что если класс определяет один (или более) следующий, следует, вероятно, явно определить все три:

  • деструктор
  • конструктор копирования
  • оператор копирующего присваивания

Эти три функции являются специальные функции-члены. Если одна из этих функций используется без предварительного объявления программистом, она будет неявно реализована компилятором со стандартной семантикой выполнения указанной операции для всех членов класса.

  • Destructor - Вызов деструкторов всех членов класса типа объекта
  • Copy конструктор - Построить все элементы объекта из соответствующих членов аргумента конструктора копии, назвав копировальные конструкторы Объект класса класса и выполнение простого назначения всех членов неклассического типа (например, int или указателя)
  • Оператор присваивания копиям - Назначить все члены объекта из соответствующих членов операции присваивания аргумента ator, вызывая операторы присваивания копий членам класса класса объекта и выполняя простое назначение всего неклассического типа (например, int или указатель).

Правило трех утверждает, что если один из них должен был быть определен программистом, то это означает, что сгенерированный компилятором версия не соответствует требованиям класса в одном случае, и это, вероятно, не подходит в других случаях. Термин «правило трех» был придуман Маршаллом Клайн в 1991 году.

Поправки к этому правилу заключаются в том, что если класс сконструирован таким образом, что Инициализация ресурсов используется (RAII) используется для всех его (нетривиальные) члены, деструктор может остаться неопределенным (также известный как Закон Большой второй). Готовым примером такого подхода является использование интеллектуальных указателей вместо простых.

Поскольку неявно сгенерированные конструкторы и операторы присваивания просто копируют все члены данных класса («мелкая копия»), следует определить явные конструкторы копирования и операторы присваивания копий для классов, которые инкапсулируют сложные структуры данных или имеют внешние ссылки, такие как указатели , если вам нужно скопировать объекты, на которые указывают члены класса. Если поведение по умолчанию («мелкая копия») на самом деле является предполагаемым, то явное определение, хотя и избыточное, будет «самодокументирующимся кодом», указывающим на то, что это было намерение, а не надзор.

Вам нужно добавить конструктор копирования и оператор присваивания копии (и ваш деструктор должен использовать delete[] вместо delete):

class A 
{ 
private: 
    int *array; 
    int size; 

public: 
    A(int s) 
     : size(s), array(new int[s]) 
    { 
     fprintf(stderr, "Allocated %p\n", array); 
    } 

    A(const A &src) 
     : size(src.size), array(new int[src.size]) 
    { 
     std::copy(src.array, src.array + src.size, array); 
     fprintf(stderr, "Allocated %p, Copied from %p\n", array, src.array); 
    } 

    ~A() 
    { 
     fprintf(stderr, "Deleting %p\n", array); 
     delete[] array; 
    } 

    A& operator=(const A &rhs) 
    { 
     A tmp(rhs); 
     std::swap(array, tmp.array); 
     std::swap(size, tmp.size); 
     return *this; 
    } 
}; 

Поскольку вы упоминаете emplace_back(), это означает, что вы используете C + +11 или более поздняя версия, а это значит, вы должны также иметь дело с ходом семантикой Rule of Five:

с появлением C++ 11 правило трех может быть расширено с правилом фи поскольку C++ 11 реализует семантику перемещения, позволяя целевым объектам захватывать (или красть) данные из временных объектов. В следующем примере также показаны новые движущиеся элементы: move constructor и move assign operator. Следовательно, для правила пяти мы имеем следующие специальные члены:

  • деструкторов
  • конструктор копирования
  • шаг конструктора
  • оператор копирующего присваивания
  • Оператор присваивания шаг

Ситуации существуют, где классы могут нуждаться в деструкторах, но не могут разумно реализовать объекты копирования и перемещения, а также скопировать и переместить назначение . Это происходит, например, когда базовый класс не поддерживает эти последние члены Big Four, но конструктор производного класса выделяет память для собственного использования. [Править] В C++ 11 это можно упростить, явно указав пять как по умолчанию.

Вы должны добавить конструктор перемещения и оператора присваивания шаг в коде выше:

class A 
{ 
private: 
    int *array; 
    int size; 

public: 
    A(int s) 
     : size(s), array(new int[s]) 
    { 
     fprintf(stderr, "Allocated %p\n", array); 
    } 

    A(const A &src) 
     : size(src.size), array(new int[src.size]) 
    { 
     std::copy(src.array, src.array + src.size, array); 
     fprintf(stderr, "Allocated %p, Copied from %p\n", array, src.array); 
    } 

    A(A &&src) 
     : size(0), array(nullptr) 
    { 
     std::swap(array, src.array); 
     std::swap(size, src.size); 
     fprintf(stderr, "Moved %p, Replaced with %p\n", array, src.array); 
    } 

    ~A() 
    { 
     fprintf(stderr, "Deleting %p\n", array); 
     delete[] array; 
    } 

    A& operator=(const A &rhs) 
    { 
     A tmp(rhs); 
     std::swap(array, tmp.array); 
     std::swap(size, tmp.size); 
     return *this; 
    } 

    A& operator=(A &&rhs) 
    { 
     std::swap(array, rhs.array); 
     std::swap(size, rhs.size); 
     return *this; 
    } 
}; 

В противном случае, вы должны стремиться к Rule of Zero вместо:

Там это предложение R Martinho Fernandes, чтобы упростить все это в Правиле 0 для C++ (прежде всего для C++ 11 &). Правило 0 утверждает, что если вы укажете какой-либо из членов по умолчанию, то ваш класс должен иметь дело исключительно с одним ресурсом. Кроме того, он должен определить все члены по умолчанию для обработки этого ресурса (или удалить член по умолчанию, если это необходимо). Таким образом, такие классы должны следовать правилу 5, описанному выше. Ресурсом может быть любое: выделенная память, файловый дескриптор, транзакция базы данных и т. Д.

Любой другой класс не должен выделять ресурсы напрямую. Кроме того, они должны опустить члены по умолчанию (или явно назначить все из них по умолчанию через = default).Любые ресурсы должны использоваться косвенно, используя классы с одним ресурсом в качестве переменных-членов/локальных переменных. Это позволяет таким классам наследовать элементы по умолчанию из объединения переменных-членов, тем самым автоматически пересылать перемещаемость/копируемость объединения всех базовых ресурсов. Поскольку собственность на 1 ресурс принадлежит только 1 переменной-члену, исключения в конструкторе не могут утечка ресурсов из-за RAII. Полностью инициализированные переменные будут иметь свои деструкторы, называемые &. Неинициализированные переменные не могли иметь никаких ресурсов для начала.

Поскольку большинство классов не имеют права собственности в качестве своей единственной заботы, большинство классов могут опустить члены по умолчанию. Это то, где правило-0 получает свое имя.

Исключите ваш ручной массив в целом и использовать std::vector вместо:

class A 
{ 
private: 
    std::vector<int> array; 

public: 
    A(int s) 
     : array(s) 
    { 
    } 
}; 

Нет необходимости явно определить копирования/перемещения конструктора, копировать/оператор присваивания перемещения или деструктор, потому что реализация по умолчанию при условии компилятор автоматически вызовет соответствующие функции vector для вас.

+0

Спасибо! Вы объяснили это с ясностью учебника. Но когда я пытаюсь выполнить код с помощью конструктора копирования, после добавления объекта 8 конструктор вызывается один раз, то конструктор копирования вызывается ** девять ** раз, а деструктор - еще девять раз. На объекте 7 конструктор копирования вызывался только один раз. На объекте 4 его называли пять раз. Разве это ужасно неэффективно? Почему это происходит? – pVinken

+0

При добавлении элементов в 'vector', если новый' size() 'будет превышать текущий' capacity() ', тогда' vector' растет, выделяя новый массив, копируя/перемещая существующие элементы к нему и освобождая старый массив, а затем вставлять новые элементы в новый массив. Преториан упоминал об этом в своем предыдущем комментарии. 'Capacity()' выращен в блоках, поэтому перераспределение не выполняется при каждой вставке. Кажется, ваш «вектор» растет в блоках по 4 за раз. Если вы заранее знаете, сколько элементов вы собираетесь добавить, вы можете 'зарезервировать()' размер массива, чтобы избежать перераспределения. –

+0

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

 Смежные вопросы

  • Нет связанных вопросов^_^