Безопасно ли push_back «динамически выделенный объект» для вектора? - PullRequest
6 голосов
/ 15 ноября 2010

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

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 ]

11 голосов
/ 15 ноября 2010

Если все, что вас волнует, это исключительная безопасность этой операции:

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

11 голосов
/ 15 ноября 2010

Ваш новый способ является более безопасным от исключений, но есть причина, по которой вы не видите, что это сделано где-либо еще.

A vector указателей владеет только указателями, этоне выражает право собственности на указанные объекты.Вы фактически передаете право собственности на объект, который «не хочет» владеть.

Большинство людей будут использовать vector из shared_ptr, чтобы правильно выразить владение, или использовать что-то вроде boost::ptr_vector.Любое из этих значений означает, что вам не нужно явно delete объекты, чьи указатели вы храните, которые подвержены ошибкам и потенциально исключают «опасные» в других точках программы.

Редактировать: Вы все равно должны быть очень осторожны со вставкой в ​​ptr_vector.К сожалению, push_back взятие необработанного указателя обеспечивает надежную гарантию, которая означает, что либо вставка прошла успешно, либо (эффективно) ничего не происходит , поэтому передаваемый объект не захватывается и не уничтожается.Версия, принимающая умный указатель по значению, определяется как вызов .release() перед вызовом строго гарантированной версии, что фактически означает, что она может быть утечкой.

Использование vector из shared_ptr вместе с make_shared - этоГораздо проще правильно использовать.

3 голосов
/ 15 ноября 2010

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

0 голосов
/ 15 ноября 2010

Насколько устойчива ваша программа к нехватке памяти? Если вы действительно заботитесь об этом, вы должны быть готовы к броску new. Если вы не собираетесь справиться с этим, я бы не стал беспокоиться о прыжках через push_back обручи.

В общем случае, если у вас не хватает памяти, программа, вероятно, уже имеет непреодолимые проблемы, если она специально не предназначена для вечной работы в ограниченном пространстве (встроенные системы) - в этом случае вам нужно заботиться обо всех случаев.

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

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

...