2009-09-01 4 views
60

Я использую вектор указателей на объекты. Эти объекты производятся из базового класса и динамически распределяются и сохраняются.Как избежать утечек памяти при использовании вектора указателей на динамически выделенные объекты в C++?

Например, у меня есть что-то вроде:

vector<Enemy*> Enemies; 

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

enemies.push_back(new Monster()); 

Что вещи, о которых я должен знать, чтобы избежать утечек памяти и других проблем?

+0

Вы используете C++? –

+0

Может быть, родной английский говорящий может расшифровать то, что вы хотите сказать, но я потерян. Во-первых, вы говорите об утечках памяти -> зависит от языка/платформы; Я ожидаю, что вы имеете в виду C++. Облегчение утечек памяти уже обсуждалось (http://stackoverflow.com/search?q=c%2B%2B+raii). Для правильной работы вам нужен виртуальный деструктор для удаления из базового типа. – gimpf

+1

Что вы подразумеваете под "векторами указателей"? Вы имеете в виду «векторы ** ** указателей»? –

ответ

137

std::vector будет управлять памятью для вас, как всегда, но эта память будет иметь указатели, а не объекты.

Это означает, что ваши классы будут потеряны в памяти, как только ваш вектор выйдет из сферы действия. Например:

#include <vector> 

struct base 
{ 
    virtual ~base() {} 
}; 

struct derived : base {}; 

typedef std::vector<base*> container; 

void foo() 
{ 
    container c; 

    for (unsigned i = 0; i < 100; ++i) 
     c.push_back(new derived()); 

} // leaks here! frees the pointers, doesn't delete them (nor should it) 

int main() 
{ 
    foo(); 
} 

Что вам нужно сделать, это убедиться, что вы удалите все объекты до того, как вектор выходит из области видимости:

#include <algorithm> 
#include <vector> 

struct base 
{ 
    virtual ~base() {} 
}; 

struct derived : base {}; 

typedef std::vector<base*> container; 

template <typename T> 
void delete_pointed_to(T* const ptr) 
{ 
    delete ptr; 
} 

void foo() 
{ 
    container c; 

    for (unsigned i = 0; i < 100; ++i) 
     c.push_back(new derived()); 

    // free memory 
    std::for_each(c.begin(), c.end(), delete_pointed_to<base>); 
} 

int main() 
{ 
    foo(); 
} 

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

Лучше было бы, если бы указатели удалили себя. Тезисы называются умными указателями, а стандартная библиотека предоставляет std::unique_ptr и std::shared_ptr.

std::unique_ptr представляет собой уникальный (не разделенный, один владелец) указатель на некоторый ресурс. Это должен быть ваш интеллектуальный указатель по умолчанию и полная полная замена любого использования неопытного указателя.

auto myresource = /*std::*/make_unique<derived>(); // won't leak, frees itself 

std::make_unique отсутствует стандарт по надзору в C++ 11, но вы можете сделать его самостоятельно. Для того, чтобы непосредственно создать unique_ptr (не рекомендуется более make_unique, если вы можете), сделайте следующее:

std::unique_ptr<derived> myresource(new derived()); 

Уникальные указатели имеют только перемещение семантику; они не могут быть скопированы:

auto x = myresource; // error, cannot copy 
auto y = std::move(myresource); // okay, now myresource is empty 

И это все, что нам нужно использовать его в контейнере:

#include <memory> 
#include <vector> 

struct base 
{ 
    virtual ~base() {} 
}; 

struct derived : base {}; 

typedef std::vector<std::unique_ptr<base>> container; 

void foo() 
{ 
    container c; 

    for (unsigned i = 0; i < 100; ++i) 
     c.push_back(make_unique<derived>()); 

} // all automatically freed here 

int main() 
{ 
    foo(); 
} 

shared_ptr имеет справочно-счетных копирования семантику; он позволяет нескольким владельцам совместно использовать объект. Он отслеживает, сколько shared_ptr s существует для объекта, а когда последний перестает существовать (число отсчитывается до нуля), оно освобождает указатель. Копирование просто увеличивает счетчик ссылок (и перемещает право собственности на более низкую, почти бесплатную стоимость). Вы делаете их с std::make_shared (или непосредственно, как показано выше, но потому, что shared_ptr должен делать внутренние выделения, как правило, более эффективно и технически безопаснее использовать make_shared).

#include <memory> 
#include <vector> 

struct base 
{ 
    virtual ~base() {} 
}; 

struct derived : base {}; 

typedef std::vector<std::shared_ptr<base>> container; 

void foo() 
{ 
    container c; 

    for (unsigned i = 0; i < 100; ++i) 
     c.push_back(std::make_shared<derived>()); 

} // all automatically freed here 

int main() 
{ 
    foo(); 
} 

Помните, что вы вообще хотите использовать std::unique_ptr по умолчанию, так как это более легкий. Кроме того, std::shared_ptr может быть построен из std::unique_ptr (но не наоборот), поэтому нормально начинать с малого.

В качестве альтернативы, можно использовать контейнер, созданный для хранения указателей на объекты, такие как boost::ptr_container:

#include <boost/ptr_container/ptr_vector.hpp> 

struct base 
{ 
    virtual ~base() {} 
}; 

struct derived : base {}; 

// hold pointers, specially 
typedef boost::ptr_vector<base> container; 

void foo() 
{ 
    container c; 

    for (int i = 0; i < 100; ++i) 
     c.push_back(new Derived()); 

} // all automatically freed here 

int main() 
{ 
    foo(); 
} 

Хотя boost::ptr_vector<T> имел очевидное применение в C++ 03, я не могу говорить об актуальности в настоящее время потому что мы можем использовать std::vector<std::unique_ptr<T>>, возможно, с небольшими сопоставимыми накладными расходами, но это требование должно быть проверено.

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

Как по умолчанию в игре, я бы, вероятно, пошел с std::vector<std::shared_ptr<T>>. Мы ожидаем совместного использования, это достаточно быстро, пока профилирование не говорит иначе, это безопасно и легко использовать.

+2

Если он на самом деле пишет gamecode (в качестве примера вида alludes), то указатель, подсчитанный ref (или, тем не менее, увеличивающий добавленный общий указатель), вероятно, слишком дорогостоящий. Постоянный объем памяти (особенно для объектов AI) является более высокой целью дизайна чем удаление цикла for для освобождения. –

+0

Какой я должен выбрать b/w Указатель содержит и общие указатели и почему? – akif

+5

@ Dan: Так или иначе вам придется делать очистку, и если это слишком медленно, вопрос заключается не в том, как это сделать, а в том, как избежать необходимости делать это в первую очередь. Если вы не можете обойти это, сначала используйте самый чистый путь, затем измерьте и только попытайтесь улучшить впоследствии. Boost означает несколько тысяч пар острых глаз, улучшающих код. Трудно это превзойти: я видел, что «shared_ptr» boost's превосходит собственный интеллектуальный указатель, используя специализированный распределитель в 3D-приложениях с интенсивным использованием процессоров/графических процессоров. Пока вы не измеряете, вы никогда не знаете ... – sbi

9

Я предполагаю следующее:

  1. Вы испытываете вектор как вектор < базы *>
  2. Вы раздвигают указатели на этот вектор после выделения объектов на куче
  3. Вы хотите сделать push_back производного * указателя на этот вектор.

следующие вещи приходят на ум:

  1. Вектор не отпустит память объекта, на который указывает указатель. Вы должны удалить его самостоятельно.
  2. Ничего особенного для вектора, но деструктор базового класса должен быть виртуальным.
  3. vector < base *> и vector < полученных *> являются двумя совершенно разными типами.
+0

Ваши предположения абсолютно правильны. Извините, я не смог объяснить правильно. Есть ли еще что-нибудь? – akif

+1

Если возможно, избегайте указателей raw и используйте методы, описанные в ответе GMan. – Naveen

9

Проблема с использованием vector<T*> заключается в том, что всякий раз, когда вектор неожиданно выходит из области видимости (например, когда генерируется исключение), вектор очищается после себя, но это освобождает только память, которую он управляет, для удержания указателя , а не память, которую вы выделили для указателей. Таким образом, GMan's delete_pointed_to function имеет ограниченное значение, так как он работает только тогда, когда ничего не происходит.

Что вам нужно сделать, это использовать смарт-указатель:

vector< std::tr1::shared_ptr<Enemy> > Enemies; 

(. Если станд Lib поставляется без TR1, использовать boost::shared_ptr вместо) для очень редких случаев угловых (циклических ссылок) За исключением этого просто устраняет проблему времени жизни объекта.

Редактировать: Обратите внимание, что GMan в своем подробном ответе также упоминает об этом.

+0

Я действительно закрывал интеллектуальные указатели сразу после этого раздела ...: P – GManNickG

+1

@GMan: Я полностью прочитал ваш ответ и увидел это. Я бы только упомянул о возможности «delete_pointer_to», не уточняя этого, поскольку он настолько уступает. Я почувствовал необходимость поставить готовое решение в короткий, простой ответ «сделай это». (Контейнеры указателя Boost - хорошая альтернатива, хотя, и я давал им возвышение, чтобы упомянуть их.) Извините, если вы ошиблись. – sbi

+2

Я думаю, что ваша точка очень хорошая, на самом деле. Должен ли я редактировать его? На данный момент я не уверен. Если я отредактирую свой ответ, чтобы он был более полным, я чувствую, что я «ворую» репутацию у других людей. – GManNickG

-1

Одно дело быть очень осторожным, если есть два объекта Monster() DERIVED, содержимое которых идентично по значению. Предположим, что вы хотели удалить объекты Monster DUPLICATE из вашего вектора (указатели класса BASE в объекты DERIVED Monster). Если вы использовали стандартную идиому для удаления дубликатов (сортировка, уникальность, стирание: см. LINK # 2), вы столкнетесь с проблемами утечки памяти и/или дублирующими проблемами удаления, что может привести к ВОССТАНОВЛЕНИЯ СЕГМЕНТАЦИИ (я лично видел эти проблемы на LINUX)

Проблема с std :: unique() заключается в том, что дубликаты в диапазоне [duplicatePosition, end] [включительно, исключая] в конце вектора не определены как?. Что может случиться, так это то, что эти неопределенные ((?) Элементы могут быть дополнительными дублирующими или отсутствующими дубликатами.

Проблема заключается в том, что std :: unique() не предназначен для обработки вектора указателей должным образом. что std :: unique копирует uniques от конца вектора «вниз» к началу вектора. Для вектора простых объектов это вызывает COPY CTOR, и если COPY CTOR написан правильно, нет проблемы с памятью утечки.Но когда его вектор указателей, то нет КОПИРОВАНИЯ CTOR, кроме «побитовой копии», и поэтому сам указатель просто копируется.

Существуют способы устранения утечки памяти, отличной от использования умного указателя. Один из способов написать собственную слегка измененную версию std :: unique() как «your_company :: unique()». ck заключается в том, что вместо копирования элемента вы должны поменять два элемента. И вы должны быть уверены, что вместо сравнения двух указателей вы вызываете BinaryPredicate, который следует за двумя указателями на сам объект, и сравнивайте содержимое этих двух производных объектов «Monster».

1) @SEE_ALSO: http://www.cplusplus.com/reference/algorithm/unique/

2) @SEE_ALSO: What's the most efficient way to erase duplicates and sort a vector?

вторая ссылка превосходно написана, и будет работать на станд :: вектор, но есть утечки памяти, повторяющиеся FreeS (иногда приводит к СЕГМЕНТАЦИИ) для std :: vector

3) @SEE_ALSO: valgrind (1). Это средство «утечки памяти» на LINUX поражает тем, что он может найти! Я настоятельно рекомендую использовать его!

Я надеюсь опубликовать хорошую версию «my_company :: unique()» в будущем посте. Прямо сейчас, это не идеально, потому что я хочу, чтобы версия 3-arg имела BinaryPredicate, чтобы работать без проблем как для указателя функции, так и для FUNCTOR, и у меня возникают некоторые проблемы с обработкой должным образом. ЕСЛИ я не могу решить эти проблемы, я опубликую то, что у меня есть, и пусть сообщество пойдет на улучшение того, что я сделал до сих пор.

+0

Это, похоже, вообще не отвечает на вопрос.Если все, что вас беспокоит, это возможность нескольких указателей на один и тот же объект, вы должны просто использовать интеллектуальный указатель с подсчетом ссылок, например 'boost :: smart_ptr'. – beldaz

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