2010-04-30 1 views
7

Я смущен тем, как использовать деструкторы, когда у меня есть std :: vector моего класса.Проблема с деструктором C++ с std :: vector объектов класса

Так что, если я создаю простой класс следующим образом:

class Test 
{ 
private: 
int *big; 

public: 
Test() 
{ 
    big = new int[10000]; 
} 

    ~Test() 
{ 
    delete [] big; 
} 
}; 

Тогда в моей основной функции я делаю следующее:

Test tObj = Test(); 
vector<Test> tVec; 
tVec.push_back(tObj); 

Я получаю сбой во время выполнения в деструкторе теста, когда я выходить из сферы действия. Почему это и как я могу освободить свою память?

+1

как-то мой DevC++ «спас» меня от этих чумок. Этот код [http://www.ideone.com/EHZBV] отлично работает на DevC++ в Windows. Любые идеи, почему это может произойти? Это не хорошо. Я хочу, чтобы он разбился, когда он должен был сбой! – Lazer

ответ

15

Вашей проблемы находится здесь:

Test tObj = Test(); 

Test() создает временный Test объект, который затем копируется в tObj. На этом этапе оба tObj и временный объект имеют big, чтобы указать на массив. Затем временный объект уничтожается, что вызывает деструктор и уничтожает массив. Поэтому, когда tObj уничтожается, он пытается снова уничтожить уже уничтоженный массив.

Кроме того, когда tVec уничтожен, он уничтожит его элементы, поэтому уже уничтоженный массив будет снова уничтожен.

Необходимо определить конструктор копирования и оператор присваивания, чтобы при копировании объекта Test массив big был скопирован или имеет некоторый счет ссылок, чтобы он не уничтожался до тех пор, пока все владельцы не будут уничтожены.

легко исправить, чтобы определить свой класс как это:

class Test 
{ 
private: 
std::vector<int> big; 

public: 
Test(): big(10000) {} 
}; 

В этом случае вам не нужно будет определить какой-либо деструктор, конструктор копирования или оператор присваивания, поскольку std::vector<> член будет заботиться из всего. (Но учтите, что это означает, что 10 000 байт распределяются и копируются всякий раз, когда вы копируете экземпляр Test.)

+0

Правильно, но не в деталях. Тест tObj = Test() не создает временный объект. Конструктор копирования вызывается в tVec.push_back (tObj); – shura

+2

@shura: выражение 'Test()' в C++ создает временный объект по определению. Составителям разрешено оптимизировать его, но в общем случае создается временное. – AnT

+0

@ AndreyT: Я не понимаю ваши «по определению». Вы сами говорите, что компиляторам разрешено его оптимизировать. И они это делают. Развертывает ли определение в этот момент? Какой компилятор вы используете, который вызывает конструктор копирования для теста o = Test()? – shura

21

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

Теперь, когда вы отбрасываете свой объект в вектор, он неявно копируется с помощью конструктора копирования. Это приводит к двум объектам, указывающим на один и тот же массив ints! Итак, в конце два деструктора пытаются удалить один и тот же массив - BANG.

Всякий раз, когда вы определяете класс, который владеет членами через указатели *, кроме деструктора, вы должны должен также определить для него конструктор копирования. Update: и оператор присваивания, по той же причине (спасибо @James :-)

UPDATE2: тривиальным образом, чтобы обойти все эти ограничения, чтобы определить статический массив вместо динамически выделяется один:

class Test 
{ 
private: 
    int big[10000]; 
    // no need for constructors, destructor or assignment operator 
}; 

Однако, лучшая практика заключается в использовании std::vector<int> вместо массива.

*, который содержит указатели на член с семантикой собственности (благодаря @Steve Джессопу для осветления)

+0

Точно. Когда тестовый объект добавляется в вектор, он копируется с помощью конструктора копирования. Вы должны потерпеть крах, если попытаетесь также получить доступ к массиву векторного объекта. – MatsT

+0

«Всякий раз, когда вы определяете класс, содержащий элементы указателя» - с семантикой собственности. Указатель без семантики собственности не требует особого обращения с классом, но, конечно, вызывающий должен убедиться, что ссылка остается действительной до тех пор, пока объект может ее использовать. –

+0

@Steve, для меня сдерживание подразумевало право собственности, но вы правы, я попытался сделать его более ясным. –

0

Без конструктора-копии вектор создаст плоскую копию вашего объекта. Это приводит к двум объектам типа Test, ссылающимся на тот же массив big. Первый экземпляр удаляет массив при его уничтожении, а затем второй экземпляр пытается разыменовать удаленный указатель, что является неопределенным поведением.

0
Test tObj = Test(); 

Это неправильно и должно быть, поскольку это не создает копии:

Test tObj; 

Это также создать много копий:

vector<Test> tVec; 
tVec.push_back(tObj); 

Так что, если вы бесплатно один целочисленный массив, вы освободите все массивы. И следующее удаление не удастся.

Что вам нужно либо:

  • использовать копию конструктора, чтобы иметь для каждого класса отдельного массива

  • Почему использовать указатель?

class Test  
{ 
private: 
    int big[10000];  
public: 

}; 

Это будет работать нормально.

+0

Ваше предложение, как правило, работает нормально, но есть платформы, где помещать 'int [10000]' в стеке вызовет проблемы. Выделение больших объектов из кучи обычно безопаснее. –

+0

Трудно точно узнать, какую платформу использует оригинальный плакат, но, возможно, если он действительно знал, почему он должен использовать распределение кучи в этом случае, возможно, он не допустил бы своих следующих ошибок. Ну, я не знаю, может быть, он должен просто использовать std :: vector? И, конечно же, вы можете просто создать (умные) указатели теста, чтобы избежать всех ненужных копий – Nikko

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