Как объект-контейнер, такой как вектор в stl, уничтожается, даже если они созданы в куче?
РЕДАКТИРОВАТЬ
Если контейнер содержит указатели, то как уничтожить эти объекты указателя





Как и любой другой объект в куче, его нужно уничтожить вручную (с помощью delete).
Когда вектор выходит за пределы области видимости, компилятор вызывает его деструктор, который, в свою очередь, освобождает выделенную память в куче.
Это несколько неправильное название. Вектор, как и большинство контейнеров STL, состоит из двух логических частей.
Хотя №2 настраивается, он почти всегда находится в куче. Однако №1 может находиться либо в стеке, либо в куче, это просто зависит от того, как он распределяется. Например
void foo() {
vector<int> v;
v.push_back(42);
}
В этом случае часть №1 находится в стеке.
Как теперь уничтожить №2? Когда первая часть вектора разрушается, она уничтожает и вторую часть. Это делается путем удаления базового массива внутри деструктора векторного класса.
Чтобы ответить на ваш первый вопрос:
В классах STL нет ничего особенного (надеюсь). Они работают точно так же, как и другие классы шаблонов. Таким образом, они не уничтожаются автоматически, если они размещены в куче, потому что C++ не имеет на них сборщика мусора (если вы не сообщите об этом каким-то причудливым бизнесом autoptr или чем-то еще). Если вы разместите его в стеке (без нового), он, скорее всего, будет автоматически управляться C++.
Что касается вашего второго вопроса, вот очень простой класс ArrayOfTen, демонстрирующий основы типичного управления памятью в C++:
/* Holds ten Objects. */
class ArrayOfTen {
public:
ArrayOfTen() {
m_data = new Object[10];
}
~ArrayOfTen() {
delete[] m_data;
}
Object &operator[](int index) {
/* TODO Range checking */
return m_data[index];
}
private:
Object *m_data;
ArrayOfTen &operator=(const ArrayOfTen &) { }
};
ArrayOfTen myArray;
myArray[0] = Object("hello world"); // bleh
По сути, класс ArrayOfTen хранит в куче внутренний массив из десяти элементов Object. Когда в конструкторе вызывается new [], в куче выделяется пространство для десяти объектов и создаются десять объектов. Аналогично, когда в деструкторе вызывается delete [], десять объектов деконструируются, а затем освобождается ранее выделенная память.
Для большинства (всех?) Типов STL изменение размера выполняется «за кулисами», чтобы обеспечить достаточный объем памяти для размещения ваших элементов. Вышеупомянутый класс поддерживает только массивы из десяти объектов. По сути, это очень ограничивающий typedef Object [10].
Контейнер указателя STL НЕ будет очищать указанные данные. Это только очистит пространство, в котором находится указатель. Если вы хотите, чтобы вектор очищал данные указателя, вам нужно использовать какую-то реализацию интеллектуального указателя:
{
std::vector<SomeClass*> v1;
v1.push_back(new SomeClass());
std::vector<boost::shared_ptr<SomeClass> > v2;
boost::shared_ptr<SomeClass> obj(new SomeClass);
v2.push_back(obj);
}
Когда эта область действия закончится, оба вектора освободят свои внутренние массивы. v1 приведет к утечке SomeClass, который был создан, поскольку в массиве находится только указатель на него. v2 не приведет к утечке данных.
Если у вас есть vector<T*>, ваш код должен удалить эти указатели перед удалением вектора: в противном случае произойдет утечка памяти.
Знайте, что C++ не выполняет сборку мусора, вот пример того, почему (извиняюсь за синтаксические ошибки, прошло некоторое время с тех пор, как я написал C++):
typedef vector<T*> vt;
⋮
vt *vt1 = new vt, *vt2 = new vt;
T* t = new T;
vt1.push_back(t);
vt2.push_back(t);
⋮
delete vt1;
Последняя строка (delete vt1;) явно не должна удалять содержащийся в ней указатель; в конце концов, это тоже в vt2. Так что это не так. И удаление vt2 тоже не будет.
(Если вам нужен векторный тип, который удаляет указатели при уничтожении, такой тип, конечно, можно записать. Вероятно, был. Но остерегайтесь указателей удаления, которые кто-то еще держит копию.)
Если вы храните указатели в классах контейнеров STL, вам необходимо вручную удалить их до того, как объект будет уничтожен. Это можно сделать, перебирая весь контейнер и удаляя каждый элемент, или используя какой-то класс интеллектуального указателя. Однако не используйте auto_ptr, так как он вообще не работает с контейнерами.
Хорошим побочным эффектом этого является то, что вы можете хранить несколько контейнеров указателей в своей программе, но только эти объекты принадлежат одному из этих контейнеров, и вам нужно очистить только этот один контейнер.
Самый простой способ удалить указатели - это сделать:
for (ContainerType::iterator it(container.begin()); it != container.end(); ++it)
{
delete (*it);
}
Это не рекомендуется (если только не является частью деструктора), поскольку это не является безопасным для исключений. Вы должны использовать контейнер, который понимает, что содержит указатель.
Как это не безопасно для исключений? Если ContainerType представляет собой что-то вроде std :: vector <std :: string *>, например, тогда это совершенно правильный код для добавления как деструктора, так и метода, подобного «очистке» или «сбросу».
Используйте либо интеллектуальные указатели внутри вектора, либо используйте ptr_vector boost. Он автоматически освободит выделенные объекты внутри него. Также есть карты, наборы и т. д.
http://www.boost.org/doc/libs/1_37_0/libs/ptr_container/doc/ptr_vector.html и основной сайт: http://www.boost.org/doc/libs/1_37_0/libs/ptr_container/doc/ptr_container.html
Чтобы удалить указанные элементы, я написал простой функтор:
template<typename T>
struct Delete {
void operator()( T* p ) const { delete p; }
};
std::vector< MyType > v;
// ....
std::for_each( v.begin(), v.end(), Delete<MyType>() );
Но вам следует отказаться от общих указателей, когда содержимое вектора должно быть ... эээ ... общим. Да.
Стандартные контейнеры STL помещают копию исходного объекта в контейнер с помощью конструктора копирования. Когда контейнер разрушается, деструктор каждого объекта в контейнере также вызывается для безопасного уничтожения объекта.
Точно так же обрабатываются указатели. Дело в том, что указатели - это данные POD. Конструктор копирования для указателя просто копирует адрес, а данные POD не имеют деструктора. Если вы хотите, чтобы контейнер управлял указателем, вам необходимо:
Я предпочитаю контейнер указателя:
Контейнеры-указатели такие же, как контейнеры STL, за исключением того, что вы помещаете в них указатели, но контейнер затем становится владельцем объекта, на который указывает указатель, и, таким образом, освобождает объект (обычно путем вызова delete) при уничтожении контейнера.
Когда вы обращаетесь к членам контейнера ptr, они возвращаются по ссылке, поэтому они ведут себя так же, как стандартный контейнер для использования в стандартных алгоритмах.
int main()
{
boost::ptr_vector<int> data;
data.push_back(new int(5));
data.push_back(new int(6));
std::cout << data[0] << "\n"; // Prints 5.
std::cout << data[1] << "\n"; // Prints 6.
} // data deallocated.
// This will also de-allocate all pointers that it contains.
// by calling delete on the pointers. Therefore this will not leak.
Следует также отметить, что интеллектуальные указатели в контейнере являются допустимой альтернативой, к сожалению, std :: auto_ptr <> не является допустимым выбором интеллектуального указателя для этой ситуации.
Это связано с тем, что контейнеры STL предполагают, что содержащиеся в них объекты можно копировать, к сожалению, std :: auto_ptr <> не копируется в традиционном смысле, поскольку он уничтожает исходное значение при копии, и, следовательно, источник копии не может быть константным.
Контейнеры STL похожи на любые другие объекты, если вы создадите один из них, он будет создан в стеке:
std::vector<int> vec(10);
Как и любая другая переменная стека, она находится только в области действия функции, в которой она определена, и ее не нужно удалять вручную. Деструктор контейнеров STL вызовет деструктор всех элементов в контейнере.
Хранение указателей в контейнере - непростая задача. Поскольку указатели не имеют деструкторов, я бы сказал, что вы никогда не захотите помещать необработанные указатели в контейнер STL. Сделать это безопасным способом будет очень сложно, вам придется засорять свой код блоками try {} finally {}, чтобы гарантировать, что содержащиеся указатели всегда освобождаются.
Итак, что вы должны помещать в контейнеры вместо необработанных указателей? +1 jmucchiello за воспитание boost :: shared_ptr. boost :: shared_ptr безопасно использовать в контейнерах STL (в отличие от std :: auto_ptr). Он использует простой механизм подсчета ссылок и безопасен для структур данных, не содержащих циклов.
Что вам понадобится для структур данных, содержащих циклы? В этом случае вы, вероятно, захотите перейти к сборке мусора, что по сути означает использование другого языка, такого как Java. Но это другое обсуждение. ;)
Да, есть: boost :: ptr_vector. (см. контейнеры boost ptr)