Безопасно ли push_back "динамически выделенный объект" для вектора?
Всякий раз, когда мне нужно добавить динамически размещенный объект в вектор, я делаю это следующим образом:
class Foo { ... };
vector<Foo*> v;
v.push_back(new Foo);
// do stuff with Foo in v
// delete all Foo in v
Это просто сработало, и многие другие, кажется, делают то же самое.
Сегодня я узнал, что vector::push_back может генерировать исключение. Это означает, что приведенный выше код не является безопасным для исключения.:-(Так что я придумал решение:
class Foo { ... };
vector<Foo*> v;
auto_ptr<Foo> p(new Foo);
v.push_back(p.get());
p.release();
// do stuff with Foo in v
// delete all Foo in v
Но проблема в том, что новый путь многословен, утомителен, и я вижу, что никто этого не делает. (По крайней мере, не вокруг меня...)
Должен ли я пойти с новым путем?
Или я могу просто придерживаться старого способа?
Или есть лучший способ сделать это?
4 ответа
Если все, что вас волнует, это исключительная безопасность этой операции:
v.reserve(v.size()+1); // reserve can throw, but that doesn't matter
v.push_back(new Foo); // new can throw, that doesn't matter either.
Вопрос о векторе, ответственном за освобождение объектов, на которые указывает его содержимое, - отдельная вещь, я уверен, что вы получите много советов по этому поводу;-)
Изменить: хм, я собирался процитировать стандарт, но я на самом деле не могу найти необходимую гарантию. Я ищу то, что push_back
не будет выбрасывать, если (а) он не должен перераспределить (что мы знаем, что это не произойдет из-за емкости), или (б) конструктор из T бросков (который мы знаем, что не будет, так как T является типом указателя), Звучит разумно, но разумно!= Гарантировано.
Итак, если на этот вопрос нет положительного ответа:
этот код зависит от реализации, не делающей ничего слишком "творческого". Если это не так, то ваше решение вопроса можно сформулировать так:
template <typename T, typename Container>
void push_back_new(Container &c) {
auto_ptr<T> p(new T);
c.push_back(p.get());
p.release();
}
Использование тогда не слишком утомительно:
struct Bar : Foo { };
vector<Foo*> v;
push_back_new<Foo>(v);
push_back_new<Bar>(v);
Если это действительно заводская функция, а не new
тогда вы можете изменить шаблон соответственно. Передача множества разных списков параметров в разных ситуациях будет затруднена.
Ваш новый способ более безопасен для исключений, но есть причина, по которой вы не видите, что это сделано где-то еще.
vector
из указателей только владеет указателями, он не выражает владение указанными объектами. Вы фактически освобождаете право собственности на объект, который не хочет владеть.
Большинство людей будут использовать vector
из shared_ptr
правильно выразить право собственности или использовать что-то вроде boost::ptr_vector
, Любое из них означает, что вам не нужно явно delete
объекты, чьи указатели вы храните, которые подвержены ошибкам и потенциально исключают "опасные" в других точках программы.
Изменить: Вы все еще должны быть очень осторожны с вставкой в ptr_vector
, К несчастью, push_back
взятие необработанного указателя обеспечивает надежную гарантию, которая означает, что либо вставка завершается успешно, либо (эффективно) ничего не происходит, поэтому передаваемый объект не захватывается и не уничтожается. Версия, принимающая умный указатель по значению, определяется как вызывающая .release()
перед вызовом строго гарантированной версии, которая фактически означает, что она может просочиться.
Используя vector
из shared_ptr
вместе с make_shared
гораздо проще правильно использовать.
Предпочтительный способ сделать это - использовать контейнер умных указателей, например, std::vector<std::shared_ptr<Foo> >
или std::vector<std::unique_ptr<Foo> >
(shared_ptr
можно также найти в Boost и C++ TR1; std::unique_ptr
эффективно ограничен C++0x).
Другой вариант - использовать контейнер, который владеет динамическими объектами, такими как контейнеры, предоставляемые библиотекой Boost Pointer Containers.
Насколько устойчива к нехватке памяти ваша программа? Если вы действительно заботитесь об этом, вы должны быть готовы к new
бросить также. Если вы не собираетесь справиться с этим, я не буду беспокоиться о том, чтобы прыгать через push_back
обручи.
В общем случае, если у вас не хватает памяти, программа, вероятно, уже сталкивается с непреодолимыми проблемами, если она специально не предназначена для вечной работы в условиях ограниченного пространства (встроенные системы) - в этом случае вам нужно заботиться обо всех этих случаях.
Если это относится к вам, у вас может быть длительный обзор кода и цикл повторного тестирования. Я думаю, что вы в порядке, чтобы следовать практике своей команды здесь, хотя.
Как уже отмечали другие, используя vector
Хранение необработанных указателей имеет свои проблемы, и на этом сайте и в других ответах есть множество материалов, которые помогут вам выбрать более безопасный шаблон.