|
Программирование >> Включение нужных заголовков
печивают более простую запись, они более четко выражают ваши намерения и обладают более высоким быстродействием. Против этого трудно что-либо возразить. Совет 6. Остерегайтесь странностей лексического разбора С++ Предположим, у вас имеется файл, в который записаны числа типа int, и вы хотите скопировать эти числа в контейнер 1 i st. На первый взгляд следующее решение выглядит вполне разумно: ifstream dataFileCints.dat ): list<int> data(istreamJterator<int>(dataFne). Внимание! Эта строка istreamJterator<int>()): работает не так, как вы предполагали Идея проста: передать пару istreain iterator интервальному конструктору 1 ist (совет 5), после чего скопировать числа из файла в список. Программа будет компилироваться, но во время выполнения она ничего не сделает. Она не прочитает данные из файла. Она даже не создаст список - а все потому, что вторая команда не объявляет список и не вызывает конструктор. Вместо этого она... Произойдет нечто настолько странное, что я даже не рискну прямо сказать об этом, потому что вы мне не поверите. Вместо этого я попробую объяснить суть дела постепенно, шаг за шагом. Надеюсь, вы сидите? Если нет - лучше поищите стул... Начнем с азов. Следующая команда объявляет функцию f, которая получает double и возвращает int: int f(double d): To же самое происходит и в следующей строке. Круглые скобки вокруг имени параметра d не нужны, поэтому компилятор их игнорирует: int f(double (d)): То же; круглые скобки вокруг d игнорируются Рассмотрим третий вариант объявления той же функции. В нем просто не указано имя параметра: int f(double); То же; имя параметра не указано Вероятно, эти три формы объявления вам знакомы, хотя о возможности заключать имена параметров в скобки известно далеко не всем (до недавнего времени я о ней не знал). Теперь рассмотрим еще три объявления функции. В первом объявляется функция g с параметром - указателем на функцию, которая вызывается без параметров и возвращает doubl е: int g(double (*pf)()); Функции g передается указатель на функцию То же самое можно сформулировать и иначе. Единственное различие заключается в том, что pf объявляется в синтаксисе без указателей (допустимом как в С, так и в С++): int g(double pfO); То же; pf неявно интерпретируется как указатель Как обычно, имена параметров могут опускаться, поэтому возможен и третий вариант объявления g без указания имени pf: int gCdoubleO); То же: имя параметра не указано Обратите внимание на различия между круглыми скобками вокруг имени параметра (например, параметра d во втором объявлении f) и стоящгши отдельно (как в этом примере). Круглые скобки, в которые заключено имя параметра, игнорируются, а круглые скобки, стоящие отдельно, обозначают присутствие списка параметров; они сообщают о присутствии параметра, который является указателем на функцию. После небольшой разминки с объявлениями f и g мы возвращаемся к фрагменту, с которого начинается этот совет. Ниже он приводится снова: 1ist<int> data(istreamjterator<int>(dataFi 1 e), i stream i terator<i nt>()): Держитесь и постарайтесь не упасть. Перед вами объявление функции data, возвращающей тип list<int>. Функция data получает два параметра: Первый параметр, dataFile, относится к типу istreain iterator<int>. Лишние круглые скобки вокруг dataFile игнорируются. Второй параметр не имеет имени. Он относится к типу указателя на функцию, которая вызывается без параметров и возвращает i stream i terator<i nt>. Любопытно, не правда ли? Однако такая интерпретация соответствует одному из основных правил C-i-i-: все, что может интерпретироваться как указатель на функцию, должно интерпретироваться именно так. Каждый программист с опытом работы на С++ встречался с теми или иными воплощениями этого правила. Сколько раз вы встречались с такой ошибкой: class Widget{...}: Предполагается, что у Widget имеется конструктор по умолчанию Widget w(): Какая неприятность... Вместо объекта класса Widget с именем w в этом фрагменте объявляется функция W, которая вызывается без параметров и возвращает Widget. Умение распознавать подобные ляпы - признак хорошей квалификации программиста С++. Все это по-своему интересно, однако мы нисколько не приблизились к поставленной цели: инициализировать объект 1 i st<i nt> содержимым файла. Зато теперь мы знаем, в чем заключается суть проблемы, и легко справимся с ней. Объявления формальных параметров не могут заключаться в круглые скобки, но никто не запрещает заключить в круглые скобки аргумент при вызове функции, поэтому простое добавление круглых скобок поможет компилятору увидеть происходящее под нужным углом зрения: list<int> data((istream iterator<int>(dataFile)), Обратите внимание istream iterator<int>()): на круглые скобки вокруг первого аргумента конструктора list Именно так следует объявлять данные. Учитывая практическую полезность istream iterator и интервальных конструкторов (совет 5), этот прием стоит запомнить. К сожалению, не все компиляторы знают об этом. Из нескольких протестированных компиляторов почти половина соглашалась только на неправильное объявление data без дополнительных круглых скобок! Чтобы умиротворить такие компиляторы, можно закатить глаза и воспользоваться неверным, как было показано выше, объявлением data, но это недальновидное и плохо переносимое решение. Более грамотный выход заключается в том, чтобы отказаться от модного использования анонимных объектов istreain i terator при объявлении data и просто присвоить этим итераторам имена. Следующий фрагмент работает всегда: ifstream dataFileCints.dat ): istream iterator<int> dataBegin(dataFile): istreani iterator<int> dataEnd: list<int> dataCdataBegin. dataEnd): Именованные объекты итераторов противоречат стандартному стилю программирования STL, но зато ваша программа будет однозначно восприниматься как компиляторами, так и людьми, которые с ними работают. Совет 7. При использовании контейнеров указателей, для которых вызывался оператор new, не забудьте вызвать delete для указателей перед уничтожением контейнера Контейнеры STL отличаются умом и сообразительностью. Они поддерживают итераторы для перебора как в прямом, так и в обратном направлении (begin, end, rbegi пит. д.); они могут сообщить тип хранящихся в них объектов (val ue type); они выполняют все необходимые операции управления памятью при вставке и удалении; они сообщают текущее количество элементов и максимальную вместимость (size и max size соответственно); и, конечно же, они автоматически уничтожают все хранящиеся в них объекты при уничтожении самого контейнера. Работая с такими интеллектуальными контейнерами, многие программисты вообще забывают о необходимости прибрать за собой и надеются, что контейнер выполнит за них всю грязную работу. Нередко их ожидания оправдываются, но если контейнер содержит указатели на объекты, созданные оператором new, этого не происходит. Разумеется, контейнер указателей уничтожает все хранящиеся в нем элементы при уничтожении самого контейнера, но деструктор указателя ничего не делает! Он не вызывает delete. В результате при выполнении следующего фрагмента возникает утечка ресурсов: void doSomethingO { vector<Widget*> vwp: for (int i=0:i<SOME MAGIC NUMBER:++i) vwp.push back(new Widget); Использовать vwp } Здесь происходит утечка Widget!
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |