|
Программирование >> Обобщенные обратные вызовы
обобщенные обратные вызовы Обобщенное программирование и стандартная библиотека с++ Одной из наиболее мощных возможностей С++ является поддержка обобщенного программирования. Эта возможность находит непосредственное отражение в гибкости стандартной библиотеки С++, в особенности в контейнерах, итераторах и алгоритмах, известных под названием стандартной библиотеки шаблонов (Standard Template Library - STL). Так же, как и предыдущая книга [Sutter02], эта книга начинается с задач, которые привлекают наше внимание к некоторым хорошо знакомым частям STL, в частности к векторам и строкам. Сможете ли вы избежать широко распространенных ловушек при использовании такого базового контейнера STL, как вектор? Как вы вьшолните обычные манипуляции со строками в С++? Какие уроки в плане конструирования библиотек вы сможете извлечь для себя из STL? После того как мы разберемся с предопределенными шаблонами STL, мы обратимся к более общим вопросам, связанным с шаблонами и обобщенным программированием на С+ + . Как можно избежать при разработке собственного шаблонного кода его необобщенности? Почему специализация шаблонов функций - не лучшая идея, и что следует делать вместо этого? Как корректно и переносимо добиться тех же результатов, которые дают отношения дружественности? И что нам дает ключевое слово export? Эти и другие вопросы будут рассмотрены нами в разделе, посвященном обобщенному программированию и стандартной библиотеке С++. Задача 1. Вектор: потребление и злоупотребление Сложность: 4 Почти все используют std:: vector, и это хорошо. К сожалению, многие не всегда верно понимают его семантику и в результате невольно применяют его странными, а порой и опасными способами. Сколько из перечисленных ниже проблем можно найти в ваших программах? Вопрос для новичка 1. В чем разница между строками А и В? void fС vector<int>& v ) { v[0]; A v.at(O); В Вопрос для профессионала 2. Рассмотрим следующий код. vector<int> v; V. reserveC 2 ) ; assertC v.capacityO == 2 ); v[0] = 1; v[l] = 2; for(vector<int>::i terator i = v.beginC); i<v.end(); i++){ cout *i endl; cout v[0]; V.reserveC 100 ); assertC V.capacityO == 100 ); cout v[0]; v[2] = 3; v[3] = 4; v[99i = 100; for( vector<int>::iterator i = v.beginC); i<v.end(); i++){ cout *i endl; Раскритикуйте этот код, как с точки зрения стиля, так и с точки зрения корректности. Решение Обращение к элементу вектора 1. В чем разница между строками А и В? void f( vector<int>& v ) { v[0]; A v.at(O); в в примере 1-1, если вектор v не пуст, разницы между строками А и В нет. Если же V пуст, то в строке В будет гарантированно сгенерировано исключение std: :out of range, но что произойдет в строке А, сказать невозможно. Имеется два способа обращения к элементам, содержащимся в векторе. Первый, vector<T>::at, выполняет проверку диапазона значения индекса, чтобы убедиться, что требуемый элемент действительно содержится в векторе. Не имеет никакого смысла обращение к сотому элементу вектора, в котором содержится всего 10 элементов, и если вы попытаетесь сделать это, то функция at защитит вас от неверных действий, генерируя исключение std: :out..of range. Оператор vector<T>: :operator[] может, но не обязан выполнять проверку диапазона. В стандарте об этом ничего не сказано, так что разработчик вашей стандартной библиотеки имеет полное право как добавить такую проверку, так и обойтись без нее. Если вы используете operator[] для обращения к элементу, отсутствующему в векторе, вы делаете это на свой страх и риск, и стандарт ничего не говорит о том, что может произойти в данном случае (хотя описание этой ситуации может оказаться в документации к используемой вами реализации стандартной библиотеки). Возможно ваша программа аварийно завершится, или будет сгенерировано исключение, или же программа будет продолжать работать, выдавая неверные результаты, или аварийно завершится в каком-то совершенно ином месте. Такая проверка диапазона защищает нас от множества проблем. Так почему же стандарт не требует ее выполнения в операторе operator[]? Краткий ответ прост: эффективность. Постоянная проверка диапазона может привести к накладным расходам (возможно, небольшим) во всех ваших программах, даже там, где гарантировашю не может быть нарушения границ. Согласно принципам С+ + , вы не должны платить за то, чего не используете, и поэтому проверка диапазона в операторе operator[] не является обязательной. В конкретном случае с векторами у нас есть еще одна причина для приоритета эффективности: векторы предназначены для использования вместо встроенных массивов, и поэтому они должны быть настолько же эффективны, как и массивы (в которых не выполняются проверки диапазона). Если вы хотите, чтобы такая проверка осуществлялась, - используйте функцию at. Увеличение размера вектора Теперь обратимся к примеру 1-2, который работает с vector<int> при помощи некоторых простых операций. 2. Рассмотрим следующий код. vector<int> v; V.reserveC 2 ); assert( v.capacityC) == 2 ); Раскритикуйте этот код, как с точки зрения стиля, так и с точки зрения корректности. Данная проверка связана с двумя проблемами, смысловой и стилистической. Смысловая проблема состоит в том, что проверка может сработать неверно. Почему? Потому что вызов reserve гарантирует, >гго емкость вектора становится равной как минимум 2, но может быть и больше 2. Обычно это так и есть, потому что типичная реализация вектора может всегда увеличивать внутренний буфер экспоненциально, невзирая на конкретный запрос посредством функции reserve. Поэтому корректная проверка до.тжна использовать оператор сравнения >=, а не строгое равенство. assert( V.capacityC) >= 2 ); Во-вторых, стилистическая ошибка заключается в том, что проверка избыточна. Почему? Потому что стандарт гарантирует выполнение проверяемого условия. Зачем же нужна явная проверка? Она не имеет смысла, если только вы не подозреваете о
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0.001
При копировании материалов приветствуются ссылки. |