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
существует для объекта, и когда последний перестает существовать (этот счетчик обнуляется), он освобождает указатель. Копирование просто увеличивает количество ссылок (и переносит владение переводами по более низкой, почти бесплатной цене). Вы делаете их с помощью 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>>
. В любом случае мы ожидаем совместного использования, это достаточно быстро, пока профилирование не скажет иначе, это безопасно и просто в использовании.