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