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