Именно так следует объявлять данные. Учитывая практическую полезность istream_iterator
и интервальных конструкторов (совет 5), этот прием стоит запомнить.
К сожалению, не все компиляторы знают об этом. Из нескольких протестированных компиляторов почти половина соглашалась только на data
без дополнительных круглых скобок! Чтобы умиротворить такие компиляторы, можно закатить глаза и воспользоваться неверным, как было показано выше, объявлением data
, но это недальновидное и плохо переносимое решение.
Более грамотный выход заключается в том, чтобы отказаться от модного использования анонимных объектов istream_iterator
при объявлении data
и просто присвоить этим итераторам имена. Следующий фрагмент работает всегда:
ifstream dataFile("ints.dat");
istream_iterator
istream_iterator
list
Именованные объекты итераторов противоречат стандартному стилю программирования STL, но зато ваша программа будет однозначно восприниматься как компиляторами, так и людьми, которые с ними работают.
Совет 7. При использовании контейнеров указателей, для которых вызывался оператор new, не забудьте вызвать delete для указателей перед уничтожением контейнера
Контейнеры STL отличаются умом и сообразительностью. Они поддерживают итераторы для перебора как в прямом, так и в обратном направлении (begin
, end
, rbegin
и т.д.); они могут сообщить тип хранящихся в них объектов (value_type
); они выполняют все необходимые операции управления памятью при вставке и удалении; они сообщают текущее количество элементов и максимальную вместимость (size
и max_size
соответственно); и, конечно же, они автоматически уничтожают все хранящиеся в них объекты при уничтожении самого контейнера.
Работая с такими интеллектуальными контейнерами, многие программисты вообще забывают о необходимости «прибрать за собой» и надеются, что контейнер выполнит за них всю грязную работу. Нередко их ожидания оправдываются, но если контейнер содержит new
, этого не происходит. Разумеется, контейнер указателей уничтожает все хранящиеся в нем элементы при уничтожении самого контейнера, но «деструктор» указателя ничего не делает! Он не вызывает delete
.
В результате при выполнении следующего фрагмента возникает утечка ресурсов:
void doSomething {
vector
for (int i=0; i
… // Использовать vwp
} // Здесь происходит утечка Widget!
Все элементы vwp
уничтожаются при выходе vwp
из области видимости, но это не изменяет того факта, что delete
не вызывается для объектов, созданных оператором new
. За удаление таких элементов отвечает программист, а не контейнер. Так было задумано. Только программист знает, delete
для этих указателей.
Обычно это делать нужно. На первый взгляд решение выглядит довольно просто:
void doSomethng {
vector
... // Как прежде
for (vector
delete *i;
}
Такое решение работает, если не проявлять особой разборчивости в трактовке этого понятия. Во-первых, новый цикл for
делает примерно то же, что и for_each
, но он не столь нагляден (совет 43). Во-вторых, этот код небезопасен по отношению к исключениям. Если между заполнением vwp
указателями и вызовом delete
произойдет исключение, это снова приведет к утечке ресурсов. К счастью, с обеими проблемами можно справиться.
Чтобы от for_each
-подобного цикла перейти непосредственно к for_each
, необходимо преобразовать delete
в объект функции. С этим справится даже ребенок — если, конечно, вы найдете ребенка, который захочет возиться с STL:
template
struct DeleteObject: // В совете 40 показано,
public unary_function
void operator(const T* ptr) const {
delete ptr;
}
};
Теперь становится возможным следующее:
void doSomething {
… //См. ранее
for_each(vwp.begin, vwp.end, DeleteObject
}