Безопасно ли 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 является типом указателя), Звучит разумно, но разумно!= Гарантировано.

Итак, если на этот вопрос нет положительного ответа:

Разрешено ли генерировать std::vector::push_back по любой причине, кроме неудачного перераспределения или конструкции?

этот код зависит от реализации, не делающей ничего слишком "творческого". Если это не так, то ваше решение вопроса можно сформулировать так:

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 Хранение необработанных указателей имеет свои проблемы, и на этом сайте и в других ответах есть множество материалов, которые помогут вам выбрать более безопасный шаблон.

Другие вопросы по тегам