
STD_05_STL_Algorithms.ppt
- Количество слайдов: 73
Стандартная библиотека С++ Лекция 5. Стандартная библиотека шаблонов STL – Standard Template Library. Алгоритмы. Олег Логинов
STL - Алгоритмы • STL Алгоритмы - стандартные алгоритмы, предназначенные для обработки элементов коллекций. • Под обработкой понимается выполнение таких стандартных операций, как поиск, сортировка, копирование, переупорядочение, модификация и численные расчеты. • Алгоритмы не являются методами контейнерных классов - это глобальные функции, работающие с итераторами. Контейнер У такого подхода есть одно важное достоинство: Ите рат ор Алгоритм Контейнер Итератор Контейнер атор Итер контейнера, достаточно реализовать его один раз для обобщенного типа • Вместо того чтобы реализовывать каждый алгоритм для каждого типа контейнера. более мощной и гибкой. • Такой подход сокращает объем программного кода, делая библиотеку Nortel Networks Confidential
STL - Алгоритмы Речь идет не о парадигме ООП • программирования), а об общей (объектно-ориентированного парадигме функционального программирования. Вместо того чтобы объединять данные с операциями, как в ООП, мы разделяем их па части, взаимодействующие через некоторый интерфейс. Контейнер Ите рат ор У этого подхода есть свои негативные стороны: Алгоритм Итератор • Такой подход недостаточно интуитивен. Некоторые комбинации • недопустимыми или, что структур данных и алгоритмов могут оказаться еще хуже, допустимыми, но бесполезными Контейнер И ор терат (например, обладающими недостаточным быстродействием). Очень важно изучить основные возможности и потенциальные опасности STL, чтобы использовать все достоинства библиотеки и благополучно обойти ловушки. Nortel Networks Confidential Контейнер
STL – Алгоритмы. Пример. #include <iostream> #include <vector> #include <algorithm> using namespace std; Контейнер Ите рат ор Алгоритм Итератор ор терат Контейнер И int main() { vector<int> coll; // Вставка элементов от 1 до 6 в произвольном порядке coll. push_back(2); coll. push_back(5); coll. push_back(4); coll. push_back(1); coll. push_back(6); coll. push_back(3); // Поиск и вывод минимального и максимального элементов vector<int>: : iterator pos; pos = min_element (coll. begin(), coll. end()); cout << "min: " << *pos << endl; pos = max_element (coll. begin(), coll. end()); Результат cout << "max: " << *pos << endl; . . . Nortel Networks Confidential min: 1 max: 6 Контейнер
STL – Алгоритмы. Пример. Продолжение… Контейнер Ите рат ор . . . // Сортировка всех элементов sort (coll. begin(), coll. end()); Алгоритм Итератор ор терат Контейнер И // Поиск первого элемента со значением, равным 3 pos = find (coll. begin(), coll. end(), // Интервал 3); // Значение // Перестановка найденного элемента со значением 3 // и всех последующих элементов в обратном порядке. reverse (pos, coll. end()); // Вывод всех элементов for (pos = coll. begin(); pos != coll. end(); ++pos) { cout << *pos << ' '; } cout << endl; } Nortel Networks Confidential 1 2 6 5 4 3 Результат Контейнер
Интервалы. • Любой алгоритм работает с одним или несколькими интервалами, заданными начальным и конечным итератором. it. Result = min_element(it. Beg, it. End); • Интервал может (хотя и не обязан) содержать все элементы контейнера. it. Result = min_element(coll. begin(), coll. end()); • При вызове алгоритма начало и конец интервала передаются в двух разных аргументах (вместо того, чтобы передавать всю коллекцию в одном аргументе). • Все алгоритмы работают с полуоткрытыми интервалами. Т. е. , интервал включает заданную начальную позицию, но конечная позиция в него не включается: Confidential конец) Nortel Networks [начало, Достоинства полуоткрытых интервалов обсуждались ранее…
Интервалы. it. Result = min_element(it. Beg, it. End); • Такой интерфейс чрезвычайно гибок, но потенциально опасен. Вызывающая сторона должна проследить за тем, чтобы первый и второй аргументы определяли действительный интервал, то есть итератор мог перейти от начала к концу интервала в процессе перебора элементов. - Оба итератора должны принадлежать одному контейнеру. - Начало интервала не должно находиться после его конца. Если это условие не выполняется, возможны непредсказуемые последствия, включая зацикливание или нарушение защиты памяти. В этом отношении итераторы так же ненадежны, как обычные указатели. Реализации STL могут выявлять подобные ошибки и обрабатывать их по своему усмотрению. Nortel Networks Confidential
Интервалы. Плохой пример. #include <iostream> #include <list> #include <algorithm> using namespace std; Результат max: 34 max: 35 Это работает, только пока коллекция содержит и 25, и 35, при чем 25 встречается раньше 35. int main() { list<int> coll; // Вставка элементов от 20 до 40 for (int i = 20; i <= 40; ++i) { coll. push_back(i); } // Поиск позиций со значениями 25 list<int>: : iterator pos 25, pos 35; pos 25 = find (coll. begin(), coll. end(), 25); pos 35 = find (coll. begin(), coll. end(), 35); } // // Интервал Значение // Вывод максимума по полученному интервалу [pos 25, pos 35) cout << "max: " << *max_element (pos 25, pos 35) << endl; // Вывод максимума по интервалу включающему pos 35: [pos 25, pos 35] cout << "max: " << Nortel Networks Confidential *max_element (pos 25, ++pos 35) << endl;
Интервалы. Плохой пример. #include <iostream> #include <list> #include <algorithm> using namespace std; int main() { list<int> coll; // Если нет элементов. . . // Поиск позиций со значениями 25 list<int>: : iterator pos 25, pos 35; pos 25 = find (coll. begin(), coll. end(), 25); pos 35 = find (coll. begin(), coll. end(), 35); } Результат max: ? ? ? ? ? ОШИБКА, если коллекция пустая. // // Интервал Значение = end() // Вывод максимума по полученному интервалу [end(), end()) cout << "max: " << *max_element (pos 25, pos 35) << endl; // Вывод максимума по интервалу включающему pos 35: [end(), end()] cout << "max: " << Nortel Networks Confidential *max_element (pos 25, ++pos 35) << endl;
Интервалы. Плохой пример. #include <iostream> #include <list> #include <algorithm> using namespace std; Результат max: 30 max: ? ? ? ОШИБКА, если коллекция не содержит 35. int main() { list<int> coll; // Вставка элементов от 20 до 30 for (int i = 20; i <= 30; ++i) { coll. push_back(i); } // Поиск позиций со значениями 25 list<int>: : iterator pos 25, pos 35; pos 25 = find (coll. begin(), coll. end(), 25); pos 35 = find (coll. begin(), coll. end(), 35); // // Интервал Значение = итератор ’ 25’ = end() // Вывод максимума по полученному интервалу [pos 25, end()) cout << "max: " << *max_element (pos 25, pos 35) << endl; } // Вывод максимума по интервалу включающему pos 35: [pos 25, end()] cout << "max: " << *max_element (pos 25, ++pos 35) << endl; Nortel Networks Confidential
Интервалы. Плохой пример. #include <iostream> #include <list> #include <algorithm> using namespace std; Результат max: ? ? ? ? ? ОШИБКА, если коллекция не содержит 25. int main() { list<int> coll; // Вставка элементов от 30 до 40 for (int i = 30; i <= 40; ++i) { coll. push_back(i); } // Поиск позиций со значениями 25 list<int>: : iterator pos 25, pos 35; pos 25 = find (coll. begin(), coll. end(), 25); pos 35 = find (coll. begin(), coll. end(), 35); } // // Интервал Значение = end() = итератор ’ 35’ // Вывод максимума по полученному интервалу [end(), pos 35) cout << "max: " << *max_element (pos 25, pos 35) << endl; // Вывод максимума по интервалу включающему pos 35: [end(), pos 35] cout << "max: " << *max_element (pos 25, ++pos 35) << endl; Nortel Networks Confidential
Интервалы. Плохой пример. #include <iostream> #include <list> #include <algorithm> using namespace std; Результат max: ? ? ? ? ? ОШИБКА, если 25 встречается позже 35. int main() { list<int> coll; // Вставка элементов от 40 до 20 for (int i = 40; i >= 20; --i) { coll. push_back(i); } // Поиск позиций со значениями 25 list<int>: : iterator pos 25, pos 35; pos 25 = find (coll. begin(), coll. end(), 25); pos 35 = find (coll. begin(), coll. end(), 35); } // // Интервал Значение = итератор ’ 25’ = итератор ’ 35’ // Вывод максимума по НЕДЕЙСТВИТЕЛЬНОМУ интервалу [pos 25, pos 35) cout << "max: " << *max_element (pos 25, pos 35) << endl; //…по НЕДЕЙСТВИТЕЛЬНОМУ интервалу включающему pos 35: [pos 25, pos 35] cout << "max: " << Nortel Networks Confidential *max_element (pos 25, ++pos 35) << endl;
… раз е щ Ие Интервалы. it. Result = min_element(it. Beg, it. End); • Такой интерфейс чрезвычайно гибок, но потенциально опасен. Вызывающая сторона должна проследить за тем, чтобы первый и второй аргументы определяли действительный интервал, то есть итератор мог перейти от начала к концу интервала в процессе перебора элементов. - Оба итератора должны принадлежать одному контейнеру. - Начало интервала не должно находиться после его конца. Если это условие не выполняется, возможны непредсказуемые последствия, включая зацикливание или нарушение защиты памяти. В этом отношении итераторы так же ненадежны, как обычные указатели. Реализации STL могут выявлять подобные ошибки и обрабатывать их по своему усмотрению. Nortel Networks Confidential
Как исправить предыдущий пример? ! При использовании итератора произвольного доступа проверка выполняется оператором <: if (pos 25 < pos 35) { // Действителен интервал [pos 25, pos 35). . . } else if (pos 35 < pos 25) { // Действителен интервал [pos 35, pos 25). . . } else { // Итераторы равны; // следовательно, оба итератора находятся в позиции end(). . . } Но такой подход применим только к коллекциям с итератором произвольного доступа: vector, deque, string (и обычный массив). Nortel Networks Confidential
Как исправить предыдущий пример? ! Не существует простого и быстрого способа определить порядок следования итераторов. Единственное разумное решение - провести поиск одного итератора в интервале от начала до другого итератора или от другого итератора до конца. В этом случае алгоритм слегка изменяется: вместо того чтобы искать оба значения во всем исходном интервале, мы пытаемся определить, какое значение встречается первым: pos 25 = find pos 35 = find if (pos 35 != // pos 35 (coll. begin(), coll. end(), 25); (coll. begin(), pos 25, 35); pos 25) { предшествует pos 25; действителен только интервал [pos 35, pos 25) . . . else { pos 35 = find (pos 25, coll. end(), 35); if (pos 35 != pos 25) { // pos 25 предшествует pos 35; действителен только интервал [pos 25, pos 35). . . else { // Итераторы равны; оба итератора находятся в позиции end() Эффективность подобной реализации Nortel. . . Networks Confidential } оставляет желать лучшего. }
Использование нескольких интервалов. • Некоторые алгоритмы работают сразу с несколькими интервалами. • Обычно в таких случаях задаются • начало и конец только одного интервала • для остальных интервалов задается только начало Т. о. конечная позиция других интервалов определяется по количеству элементов в первом интервале. Например, следующий вызов equal() поэлементно сравнивает все содержимое коллекции colli с элементами со 112, начиная с первого: // Вызов equal() поэлементно сравнивает все содержимое // коллекции coll 1 с элементами соll 2, начиная с первого элемента: if ( equal (coll 1. begin(), coll 1. end(), coll 2. begin()) ) {. . . } Nortel Networks Confidential
Использование нескольких интервалов. Вызывая алгоритм для нескольких интервалов, убедитесь в том, что второй и все прочие интервалы содержат не меньше элементов, чем первый интервал. equal (coll 1. begin(), coll 1. end(), coll 2. begin()); Размер приемных интервалов особенно важен для алгоритмов, осуществляющих запись в коллекцию. copy (coll 1. begin(), coll 1. end(), coll 2. begin()); Nortel Networks Confidential
Использование нескольких интервалов. Плохой пример. #include <iostream> #include <vector> #include <list> #include <algorithm> using namespace std; int main() { list<int> coll 1; vector<int> coll 2; // пустая коллекция! // Вставка элементов со значениями от 1 до 9 for (int i = 1; i <= 9; ++i) { coll 1. push_back(i); } // ОШИБКА ВРЕМЕНИ ВЫПОЛНЕНИЯ: // - перезапись несуществующих элементов в приемнике copy (coll 1. begin(), coll 1. end(), // Источник coll 2. begin()); // Приемник //. . . } Nortel Networks Confidential
Использование нескольких интервалов. Исправленный пример. . // Вставка элементов со значениями от 1 до 9 for (int i = 1; i <= 9; ++i) { coll 1. push_back(i); } // Изменение размера приемного интервала, чтобы он // были достаточен для работы алгоритма с перезаписью. coll 2. resize (coll 1. size()); // Копирование элементов из первой коллекции во вторую // - перезапись существующих элементов в приемном интервале copy (coll 1. begin(), coll 1. end(), // Источник coll 2. begin()); // Приемник } // Создание третьей коллекции с необходимым количеством элементов // - исходный размер передается в параметре deque<int> coll 3(coll 1. size()); // Копирование элементов из первой коллекции в третью copy (coll 1. begin(), coll 1. end(), // Источник coll 3. begin()); // Приемник Nortel Networks Confidential
Итераторные адаптеры. • Итераторные адаптеры - несколько специализированных итераторов, содержащихся в стандартной библиотеке С++. • Итераторы, в данном контексте, являются чистыми абстракциями. Иначе говоря, любой объект, который ведет себя как итератор, является итератором. • Итераторные адаптеры - классы, которые обладают интерфейсом итератора, но делают нечто совершенно иное. (По сравнению с обычными итераторами, которые предназначены лишь для перебора элементов коллекций. ) • Итераторные адаптеры наделяют саму концепцию итераторов рядом новых возможностей. Три стандартные разновидности итераторных адаптеров: • итераторы вставки • потоковые итераторы • обратные итераторы Nortel Networks Confidential
Итераторы вставки. • Итераторы вставки - позволяют использовать алгоритмы в режиме вставки (вместо режима перезаписи). • Итераторы вставки решают проблему с нехваткой места в приемном интервале при записи: приемный интервал просто увеличивается до нужных размеров. • Поведение итераторов вставки слегка переопределено: • Если присвоить значение элементу, ассоциированному с итератором, это значение вставляется в коллекцию, с которой связан данный итератор: *( inserter(coll 4, coll 4. begin()) ) = 5; • Операция перехода вперед (operator ++) не производит никаких действий. • Три разновидности итераторов вставки позволяют вставлять элементы в разных местах - в начале, в конце или в заданной позиции. Nortel Networks Confidential
Итераторы вставки. Пример. . list<int> coll 1; // Вставка элементов со значениями от 1 до 9 в первую коллекцию // в первую коллекцию for (int i = 1; i <= 9; ++i) { coll 1. push_back(i); } // Копирование элементов из coll 1 в coll 2 с присоединением в конец vector<int> coll 2; copy (coll 1. begin(), coll 1. end(), // Используется push_back() Источник back_inserter(coll 2)); // Приемник (vector, deque, list). // Копирование элементов coll 1 в coll 3 со вставкой в начало // - порядок следования элементов заменяется на противоположный deque<int> coll 3; copy (coll 1. begin(), coll 1. end(), //Используется push_front() Источник front_inserter(coll 3)); // Приемник (deque, list). // Копирование элементов coll 1 в coll 4 // - единственный итератор вставки, работающий // с ассоциативными контейнерами set<int> coll 4; Используется insert(). copy (coll 1. begin(), coll 1. end(), //Второй аргумент – позиция вставки. Источник Nortel Networks Confidential inserter(coll 4, coll 4. begin())); // Приемник контейнеры). (все
Итераторы вставки. • back_inserter (контейнер) Элементы присоединяются с конца в прежнем порядке с использованием функции push_back(). • front_inserter (контейнер) Элементы вставляются в начало в обратном порядке с использованием функции push_front(). • inserter (контейнер, позиция) Элементы вставляются в заданной позиции в прежнем порядке с использованием функции insert(). В ассоциативных контейнерах переданная позиция интерпретируется как рекомендация для начала поиска нужной позиции. Если переданная позиция окажется неверной, на выполнение операции может понадобиться больше времени, чем вообще без рекомендации. Nortel Networks Confidential
Потоковые итераторы. • Потоки (данных) - объекты, представляющие каналы ввода-вывода. • Потоковые итераторы - итераторы, обеспечивающие чтение из потока данных и запись в поток данных. • Потоковые итераторы позволяют интерпретировать ввод с клавиатуры как коллекцию, из которой можно читать данные. Аналогично, результаты работы алгоритма можно перенаправить в файл или на экран. … раз ще И е • Итераторы, в данном контексте, являются чистыми абстракциями. Иначе говоря, любой объект, который ведет себя как итератор, является итератором. Nortel Networks Confidential
Потоковые итераторы. Пример. #include <iostream> #include <vector> #include <string> #include <algorithm> using namespace std; int main() { vector<string> coll; // Загрузка слов из стандартного входного потока данных // - источник: все строки до конца файла (или до возникновения ошибки) // - приемник: coll (вставка) copy (istream_iterator<string>(cin), // Начало источника Используется operator <<() istream_iterator<string>(), // Конец источника back_inserter(coll)); // Приемник // Сортировка элементов sort (coll. begin(), coll. end()); // Вывод всех элементов без дубликатов (с разделением элементов символом // - источник: coll новой строки) // - приемник: стандартный вывод Используется operator >>() unique_copy (coll. begin(), coll. end(), // Источник ostream_iterator<string>(cout, "n")); // Приемник Nortel Networks Confidential }
Обратные итераторы. • Обратные итераторы – итераторы, которые работают в противоположном направлении: вызов оператора ++ на внутреннем уровне преобразуется в вызов оператора --, и наоборот. • Все контейнеры поддерживают создание обратных итераторов функциями rbegin() и rend(). • Благодаря обратным итераторам все алгоритмы получают возможность работать в обратном направлении без модификации кода. • «Нормальные» итераторы можно переключать в режим обратного перебора и наоборот. (Но при этом изменяется текущий элемент, связанный с итератором. ) аз… ер щ И е • Итераторы, в данном контексте, являются чистыми абстракциями. Иначе говоря, любой объект, который ведет Confidentialитератор, Nortel Networks себя как является итератором.
Обратные итераторы. Пример. #include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { vector<int> coll; 1 2 3 4 5 6 7 8 9 // Вставка элементов со значениями от 1 до 9 for (int i = 1; i <= 9; ++i) { coll. push_back(i); } // Вывод всех элементов в обратном порядке copy (coll. rbegin(), coll. rend(), // Источник ostream_iterator<int>(cout, " ")); // Приемник cout << endl; } Nortel Networks Confidential 9 8 7 6 5 4 3 2 1 Результат
«Удаление» элементов. Алгоритм remove(). Пример. . list<int> coll; // Вставка элементов со значениями от 6 до 1 и от 1 до 6 for (int i = 1; i <= 6; ++i) { coll. push_front(i); coll. push_back(i); } // Вывод всех элементов коллекции cout << "pre: "; copy (coll. begin(), coll. end(), ostream_iterator<int>(cout, " ")); // Источник // Приемник // «Удаление» всех элементов со значением 3 remove (coll. begin(), coll. end(), // Интервал 3); // Значение // Вывод всех элементов коллекции cout << "post: "; copy (coll. begin(), coll. end(), ostream_iterator<int>(cout, " ")); // Источник // Приемник } pre: 6 5 4 3 2 1 1 2 3 4 5 6 Nortel Networks Confidential post: 6 5 4 2 1 1 2 4 5 6 Результат
«Удаление» элементов. Алгоритм remove() не изменяет количество элементов в коллекции, для которой он вызывался. После вызова remove() функция end() возвращает прежнее значение, а функция size() прежнее количество элементов. Алгоритм remove() изменяет порядок следования элементов таким образом, каким он должен стать после удаления. На место «удаленных» элементов записаваются следующие элементы. Прежние элементы в конце коллекции, не перезаписанные алгоритмом, остаются без изменений. (На логическом уровне эти конечные элементы уже не принадлежат коллекции. ) Результат • remove() возвращает итератор, указывающий на логический новый конец коллекции. Это значение позволяет прочитать полученный интервал, уменьшить размер коллекции или Nortel Networks Confidential узнать количество удаленных элементов.
«Удаление» элементов. Алгоритм remove(). Исправленный пример. . // Удаление всех элементов со значением 3 // - сохранение нового логического конца коллекции list<int>: : iterator end = remove (coll. begin(), coll. end(), 3); // Вывод элементов полученной коллекции copy (coll. begin(), end, ostream_iterator<int>(cout, " ")); cout << endl; 6 5 4 3 2 1 6 5 4 2 1 1 number of removed 6 5 4 2 1 1 pre: // Вывод количества "удаленных" элементов cout << "number of removed elements: " << distance(end, coll. end()) << endl; // Стирание "удаленных" элементов coll. erase (end, coll. end()); // Вывод всех элементов модифицированной коллекции copy (coll. begin(), coll. end(), ostream_iterator<int>(cout, " ")); Nortel Networks Confidential } 1 2 3 4 5 6 2 4 5 6 elements: 2 2 4 5 6 Результат
«Удаление» элементов. Алгоритм remove(). Исправленный пример. При желании, можно удалить и стереть элементы одной составной командой: . . . // Удаление и стирание всех элементов со значением 3 coll. erase (remove(coll. begin(), coll. end(), 3), coll. end()); Результат // Вывод всех элементов модифицированной коллекции copy (coll. begin(), coll. end(), ostream_iterator<int>(cout, " ")); pre: 6 5 4 3 2 1 1 2 3 4 5 6 } 6 5 4 2 1 1 2 4 5 6 Почему remove() сам не вызывает erase() ? Ответ – в гибкой архитектуре STL: • remove() – алгоритм, работающий через итераторы • erase() – метод контейнера • Алгоритм не может (и не должен) вызывать напрямую методы контейнера. • Некоторые контейнеры не поддерживают функцию erase() (например, обычный массив). Nortel Networks Confidential • «Физическое» стирание не всегда нужно.
Модифицирующие алгоритмы и ассоциативные контейнеры. • Модифицирующие алгоритмы – алгоритмы, изменяющие содержимое коллекции. (Например remove(). ) Это алгоритмы, которые удаляют элементы, изменяют порядок их следования или их значения. Нельзя использовать модифицирующие алгоритмы с ассоциативными контейнерами. Т. о. ассоциативные контейнеры не могут быть приемниками. Причина проста: работая с ассоциативным контейнером, модифицирующий алгоритм мог бы изменить значения или позиции элементов, что привело бы к нарушению порядка их сортировки. Тем самым будет нарушено основное правило, согласно которому элементы ассоциативных контейнеров всегда сортируются автоматически по заданному критерию. Для сохранения упорядоченности все итераторы ассоциативных контейнеров объявляются итераторами константных значений (или ключей). Следовательно, попытки модификации элементов ассоциативного контейнера через итератор вызывают ошибки на стадии компиляции. А значит попытка вызова любого модифицируещего Nortel Networks Confidential алгоритма (например remove()) приведет к ошибке компилляции.
Удаление элементов из ассоциативных контейнеров. Как же удалить элементы из ассоциативных контейнеров? • Для удаления элементов из ассоциативных контейнеров используются собственные методы контейнера. • В каждом ассоциативном контейнере предусмотрены методы удаления элементов. Например, элементы можно удалить функцией erase(). • Контейнеры поддерживают несколько версий функции erase(). • Только та версия, единственным аргументом которой является значение удаляемого элемента (или элементов), возвращает количество удаленных элементов. Очевидно, если дубликаты в коллекции запрещены (как в множествах и отображениях), возвращаемое значение функции может быть равно только 0 или 1. Nortel Networks Confidential
Удаление элементов из ассоциативных контейнеров. Пример. . set<int> coll; // Вставка элементов со значениями от 1 до 9 for (int i = 1; i <= 9; ++i) { coll. insert(i); } // Вывод всех элементов коллекции copy (coll. begin(), coll. end(), ostream_iterator<int>(cout, " ")); cout << endl; 1 2 3 4 5 6 7 8 9 number of removed elements: 1 1 2 4 5 6 7 8 9 // Удаление (физическое) всех элементов со значением 3 // - алгоритм remove() не работает // - используем функцию erase() int num = coll. erase(3); // Вывод количества удаленных элементов cout << "number of removed elements: " << num << endl; // Вывод всех элементов модифицированной коллекции copy (coll. begin(), coll. end(), ostream_iterator<int>(cout, " ")); cout << endl; Nortel Networks Confidential } Результат
Алгоритмы и методы контейнеров. Если можно использовать алгоритм, это еще не значит, что его нужно использовать. Возможно, контейнерный класс содержит методы, которые работают гораздо эффективнее. Хорошим примером служит вызов алгоритма remove() для элементов списка. • Алгоритм remove() не знает, что он работает со списком. Поэтому он , как обычно, переупорядочивает элементы, изменяя их значения. Например, при удалении первого элемента каждому элементу присваивается значение элемента, следующего после него. • Такой подход игнорирует основное достоинство списков возможность выполнения вставки, перемещения и удаления элементов посредством модификации ссылок, а не элементов. • Для повышения эффективности операций в списках определены специальные методы для всех модифицирующих алгоритмов. Всегда используйте эти методы контейнеров вместо алгоритмов. Nortel Networks Confidential Более того, эти методы действительно исключают удаляемые элементы.
Алгоритмы и методы контейнеров. Пример. . list<int> coll; // Вставка элементов со значениями от 6 до 1 и от 1 до 6 for (int i = 1; i <= 6; ++i) { coll. push_front(i); coll. push_back(i); } // Удаление всех элементов со значением 3 // - неэффективно coll. erase (remove(coll. begin(), coll. end(), 3), coll. end()); // Удаление всех элементов со значением 4 // - эффективно coll. remove (4); } Nortel Networks Confidential
Алгоритмы и методы контейнеров. … раз е щ Ие Для обеспечения хорошего быстродействия, всегда используйте методы классов вместо алгоритмов. • Вы должны заранее знать, что некоторый контейнер содержит метод, обладающий более высоким быстродействием. • Увы, при вызове неэффективного алгоритма (например, remove()) не последует ни предупреждения, ни сообщения об ошибке. • С другой стороны, если выбрать метод, то при переходе на другой тип контейнера вам придется вносить изменения в программу. • Если не уверены, что эффективнее по быстродействию – алгоритм или метод – воспользуйтесь справочником алгоритмов. Nortel Networks Confidential
Унифицированные пользовательские функции. • Библиотека STL имеет расширяемую архитектуру. Программист может создавать собственные функции и алгоритмы для обработки коллекций. • Такие операции тоже должны быть унифицированными. • Чтобы упростить написание унифицированных функций, в каждом контейнерном классе присутствуют внутренние определения вспомогательных типов. Это очень удобно, например, при объявлении итератора. Ведь при указании типа итератора необходимо указывать тип контейнера. typename T: : iterator pos; … ние ре вто Ключевое слово typename указывает, что iterator – тип, а не значение. По • • Помимо типов iterator и const_iterator контейнеры предоставляют и другие типы, упрощающие написание унифицированных функций. (Например, тип элементов для выполнения операций с временными копиями элементов. ) Nortel Networks Confidential
Унифицированные пользовательские функции. Пример. #include <iostream> // PRINT_ELEMENTS() // - вывод необязательной строки C optcstr, после которой // - выводятся все элементы коллекции coll, // - разделенные пробелами. template <class T> inline void Print. Elements (const T& coll, const char* opt. Str = "") { typename T: : const_iterator pos; } std: : cout << opt. Str; for (pos = coll. begin(); pos != coll. end(); ++pos) { std: : cout << *pos << ' '; } std: : cout << std: : endl; vector<int> coll 1; . . . Print. Elements(coll 1, “vector elements: ”); Nortel Networks Confidential set<double> coll 2; . . . Print. Elements(coll 2, “set elements: ”);
Передача функций алгоритмам. • Некоторым алгоритмам через их аргументы могут передаваться пользовательские функции, которые вызываются в процессе внутренней работы алгоритма. • Такая возможность делает алгоритмы еще более гибкими и мощными. е… ени р вто Функциональный аргумент – аргумент (алгоритма, функции или метода класса), По • который «ведет себя как функция» . • В простейшем случае, это просто указатель на функцию. • В некоторых случаях передача функции алгоритму обязательна, в других – нет (например, когда задано значение по умолчанию для функционального аргумента, или есть перегрузка с меньшим количеством аргументов). • Функциональный аргумент может определять критерий поиска, критерий сортировки или преобразование, применяемое при копировании элементов из одной коллекции в другую. Nortel Networks Confidential
Функциональные аргументы. Пример 1. #include <iostream> #include <vector> #include <algorithm> using namespace std; 1 2 3 4 5 6 7 8 9 Результат // Функция выводит переданный аргумент void print (int elem) { cout << elem << ' '; } int main() { vector<int> coll; // Вставка элементов со значениями от 1 до 9 for (int i = 1; i <= 9; ++i) { coll. push_back(i); } // Вывод всех элементов for_each (coll. begin(), coll. end(), print); } Nortel Networks Confidential // Интервал // Операция
Функциональные аргументы. Пример 1. . // Вывод всех элементов for_each (coll. begin(), coll. end(), print); // Интервал // Операция Как это работает? ! namespace std { template <class Iterator, class Operation> Operation for_each (Iterator act, Iterator end, Operation op) { while (act != end) { // Пока не достигнут конец интервала op (*act); // - вызвать ор() для текущего элемента ++act; // - переместить итератор к следующему } // элементу return op; } } Nortel Networks Confidential
Функциональные аргументы. Пример 2. . #include "print. h" int square (int value) { return value * value; } initialized: 1 2 3 4 5 6 7 8 9 squared: 1 4 9 16 25 36 49 64 81 Результат int main() { set<int> coll 1; vector<int> coll 2; // Вставка элементов со значениями от 1 до 9 в coll 1 for (int i = 1; i <= 9; ++i) { coll 1. insert(i); } Print. Elements(coll 1, "initialized: "); // Преобразование каждого элемента при копировании из coll 1 в coll 2 // - трансформируемые значения возводятся в квадрат transform (coll 1. begin(), coll 1. end(), // Источник back_inserter(coll 2), // Приемник square); // Операция Print. Elements(coll 2, "squared: Nortel Networks Confidential } ");
Функциональные аргументы. Пример 2. . #include "print. h" int square (int value) { return value * value; } initialized: 1 2 3 4 5 6 7 8 9 squared: 1 4 9 16 25 36 49 64 81 Результат int main() { set<int> coll 1; vector<int> coll 2; // Вставка элементов со значениями от 1 до 9 в coll 1 for (int i = 1; i <= 9; ++i) { coll 1. insert(i); } Print. Elements(coll 1, "initialized: "); // Преобразование каждого элемента при копировании из coll 1 в coll 2 // - трансформируемые значения возводятся в квадрат transform (coll 1. begin(), coll 1. end(), // Источник back_inserter(coll 2), // Приемник square); // Операция Print. Elements(coll 2, "squared: Nortel Networks Confidential } ");
ие… н оре Предикаты т Пов • Предикат – вспомогательная функция, которая возвращает логическое значение (в С++ тип bool – true/false). • В алгоритмах предикаты часто опеределяют критерии сортировки и поиска. • Унарный предикат – предикат с одним аргументом. bool f(int); template<class T> bool f(const T&); • Бинарный предикат – предикат с двумя аргументами. bool f(int, int); template<class T> bool f(const T&, const T&); class A • Предикат может быть как обычной функцией, так и статическим методом какого-либо класса. { public: static bool f(int); }; • Не любая унарная или бинарная функция, возвращающая логическую величину, является действительным предикатом. STL требует, чтобы при неизменности входных данных предикат всегда давал постоянный результат. Тем самым из категории предикатов исключаются функции, Nortel Networks Confidential внутреннее состояние которых изменяется в процессе вызова.
Унарный предикат. Пример. #include <iostream> #include <list> #include <algorithm> #include <cstdlib> using namespace std; // для abs() // Предикат проверяет, является ли целое число простым bool is. Prime (int number) { // Знак числа игнорируется number = abs(number); // 0 и 1 не являются простыми числами if (number == 0 || number == 1) { return false; } // Поиск множителя, на который число делится без остатка int divisor; for (divisor = number/2; number%divisor != 0; --divisor) { ; } // Если не найдено ни одного множителя, большего 1, // проверяемое число является простым. return divisor == Nortel Networks Confidential 1; }
Продолжение… Унарный предикат. Пример. int main() { list<int> coll; 29 is first prime number found // Вставка элементов со значениями от 24 до 30 for (int i = 24; i <= 30; ++i) { coll. push_back(i); } // Поиск простого числа list<int>: : iterator pos; pos = find_if (coll. begin(), coll. end(), is. Prime); // Интервал // Предикат if (pos != coll. end()) { // Найдено простое число cout << *pos << " is first prime number found" << endl; } else { // Простые числа не найдены cout << "no prime number found" << endl; } Nortel Networks Confidential } Результат
Бинарный предикат. Пример. . class Person { public: string firstname() const; string lastname() const; . . . }; // Бинарный предикат: // - сравнивает два объекта Person bool person. Sort. Criterion (const Person& p 1, const Person& p 2) { // Первый объект Person меньше второго, // - если фамилия в первом объекте меньше фамилии во втором объекте; // - или если фамилии равны, а имя в первом объекте меньше. return p 1. lastname() < p 2. lastname() || ( ! (p 2. lastname() < p 1. lastname() ) && p 1. firstname() < p 2. firstname() ); } int main() { deque<Person> coll; . . . sort(coll. begin(), coll. end(), person. Sort. Criterion); Nortel Networks Confidential. . . } // Интервал // Критерий сортировки
ие… рен вто По Функторы. • Функтор – объект, который ведет себя как функция. (объект-функция) • Класс в С++ с перегруженным оператором () является функтором: class X { public: // или без const bool operator () (int) const; }; class X { public: // или без const bool operator () (int, int) const; }; … раз ще И е • Функциональный аргумент – аргумент, который «ведет себя как функция» . • В качестве функциональных аргументов часто используются предикаты и функторы. namespace std { template <class T, class Compare> inline const T& max (const T& a, const T& b, Compare comp) { return comp(a, b) ? b : a; } } Nortel Networks Confidential
Функтор. Пример 1. #include <iostream> #include <vector> #include <algorithm> using namespace std; 1 2 3 4 5 6 7 8 9 Результат // Простой функтор для вывода передаваемого аргумента class Print. Int { public: void operator() (int elem) const { В данном случае можно cout << elem << ' '; было бы вполне обойтись } обычной функцией. }; int main() { vector<int> coll; // Вставка элементов со значениями от 1 до 9 for (int i = 1; i <= 9; ++i) { coll. push_back(i); } // Вывод всех элементов for_each (coll. begin(), coll. end(), Print. Int Nortel Networks Confidential ()); } // Интервал // Операция
Функция и функтор. . // Вывод всех элементов for_each (coll. begin(), coll. end(), print); // Интервал // Операция - функция Сравните…. . . // Вывод всех элементов for_each (coll. begin(), coll. end(), Print. Int()); аз… ер щ И е Как это работает? ! // Интервал // Операция - функтор Создание экземпляра функтора. Вызов конструктора. namespace std { template <class Iterator, class Operation> Operation for_each (Iterator act, Iterator end, Operation op) { while (act != end) { // Пока не достигнут конец интервала op (*act); // - вызвать ор() для текущего элемента ++act; // - переместить итератор к следующему } // элементу return op; } Nortel Networks Confidential } Либо print (*act) Либо op. operator () (*act)
Функтор – «умная функция» . • Функтор - это «умная функция» , поскольку их возможности не ограничиваются вызовом оператора (). Функторы могут представлять другие функции и иметь другие атрибуты, т. е. функторы обладают состоянием. У обычных функций такая возможность отсутствует. • Одно из главных достоинств функторов - возможность их инициализации на стадии выполнения перед вызовом/использованием. • Каждому функтору соответствует свой тип. Функциональное поведение может передаваться при конструировании экземпляра функтора, или даже как параметр шаблона (если функтор – шаблон). Например, это позволяет контейнерам разных типов использовать единственный объект функции в качестве критерия сортировки. Тем самым предотвращается возможное присваивание, слияние и сравнение коллекций, использующих разные критерии сортировки. • Функторы обычно работают быстрее функций, т. к. шаблоны обычно лучше оптимизируются. Nortel Networks Confidential
Пример с обычной функцией. Задача: требуется увеличить значения всех элементов коллекции на некоторую величину. Если приращение известно на стадии компиляции, можно воспользоваться обычной функцией: void add 10 (int& elem) { elem += 10; } void f 1() { vector<int> coll; . . . for_each(coll. begin(), coll. end(), add 10); } Nortel Networks Confidential // Интервал // Операция
Пример с шаблонной функцией. Если возможны разные приращения, известные на стадии компиляции, функция оформляется в виде шаблона: template <int the. Value> void add (int& elem) { elem += the. Value; } void f 1() { vector<int> coll; . . . for_each(coll. begin(), coll. end(), add<10>); } Nortel Networks Confidential // Интервал // Операция
Пример с функтором. Если приращение определяется только во время выполнения программы, применяется функтор. (Либо функция с дополнительной глобальной переменной. ) // Функтор прибавляет к значению элемента приращение, // заданное при его инициализации class Add. Value { int the. Value; // Приращение public: // Конструктор инициализирует приращение Add. Value(int v) : the. Value(v) { } // Суммирование выполняется "вызовом функции" для элемента void operator() (int& elem) const { elem += the. Value; } }; int main() { list<int> coll; . . . // Прибавить к каждому элементу 10 for_each (coll. begin(), coll. end(), Add. Value(10)); // Интервал // Операция // Прибавить к каждому элементу значение первого элемента for_each (coll. begin(), coll. end(), // Интервал Add. Value(*coll. begin())); // Операция Nortel Networks Confidential }
Пример с несколькими экземплярами функторов. Наконец, можно иметь несколько независимых экземпляров функторов в разных состояниях. Иногда это удобно: Add. Value addx(x); // Объект функции, прибавляющий значение x Add. Value addy(y); // Объект функции, прибавляющий значение y for_each (coll. begin(), coll. end(), addx); . . . for_each (coll. begin(), coll. end(), addy); . . . for_each (coll. begin(), coll. end(), addx); Nortel Networks Confidential // Прибавление значения x // к каждому элементу // Прибавление значения у // к каждому элементу // Прибавление значения x // к каждому элементу
Стандартные функторы. Стандартная библиотека С++ содержит набор функторов для выполнения базовых операций. Примеры: set<int, less<int> > coll; // Сортировка элементов оператором < set<int, greater<int> > coll; // Сортировка элементов оператором // Изменение знака всех элементов коллекции: transform (coll. begin(), coll. end(), // Источник coll. begin(), // Приемник negate<int>()) ; // Операция // Возведение всех элементов коллекции в квадрат: transform (coll. begin(), coll. end(), // Первый источник coll. begin(), // Второй источник coll. begin(), // Приемник multiplies<int>()) ; // Операция Nortel Networks Confidential <
Функциональные адаптеры. Специальные функциональные адаптеры позволяют объединять стандартные функторы с другими значениями или использовать их в особых ситуациях. (Они же функторные адаптеры – ”functor adapters”). Пример функционального адаптера: // TEMPLATE CLASS bind 2 nd - functor adapter _Func(left, stored) template<class _Fn 2> class bind 2 nd { public: bind 2 nd(const _Fn 2& _Func, const typename _Fn 2: : second_argument_type& _Right) : op(_Func), value(_Right) {} // construct from functor and right operand _Fn 2: : result_type operator()( const typename _Fn 2: : first_argument_type& _Left) const { // apply functor to operands return op(_Left, value); } protected: _Fn 2 op; // the functor to apply typename _Fn 2: : second_argument_type value; // the right operand Nortel Networks Confidential };
Функциональные адаптеры. Пример. set<int, greater<int> > coll 1; // Сортировка по убыванию deque<int> coll 2; // Вставка элементов со значениями от 1 до 9 for (int i = 1; i <= 9; ++i) { coll 1. insert(i); } // Копирование всех элементов из coll 1 в coll 2 с умножением на 10 transform (coll 1. begin(), coll 1. end(), // Источник back_inserter(coll 2), // Приемник bind 2 nd(multiplies<int>(), 10)); // Операция // Замена значения, равного 70, на 42 replace_if (coll 2. begin(), coll 2. end(), bind 2 nd(equal_to<int>(), 70), 42); // Интервал // Критерий замены // Новое значение // Удаление всех элементов со значениями, строго меньшими 50 coll 2. erase(remove_if(coll 2. begin(), coll 2. end(), // Интервал bind 2 nd(less<int>(), 50)), // Критерий удаления coll 2. end()); coll 1: 9 8 7 6 5 4 3 2 1 coll 2: 90 80 70 60 50 40 30 20 10 replaced: 90 80 42 60 50 40 30 20 10 Nortel Networks Confidential Результат removed: 90 80 60 50
Функциональные адаптеры. аз… ер щ Ие • Функциональные адаптеры позволяют объединять стандартные функторы с другими значениями или использовать их в особых ситуациях. • Подобный подход к программированию приводит к функциональной композиции. • Все методы функторов обычно объявляются встраиваемыми (inline). Поэтому, несмотря на абстрактную функциональную запись, мы получаем хорошую производительность. Nortel Networks Confidential
Специальные функциональные адаптеры. Сушествуют и другие специальные разновидности функциональных адаптеров. Например, некоторые адаптеры позволяют вызвать определенный метод класса для каждого элемента коллекции: for_each (coll. begin(), coll. end(), // Интервал mem_fun_ref (&Person: : save)); // Операция Адаптер mem_fun_ref вызывает заданный метод класса для того элемента, для которого этот объект вызывается. В приведенном примере для каждого элемента коллекции coll вызывается метод save() класса Person. Разумеется, эта конструкция работает только в том случае, если элемент относится к тину Person или производному от него. Nortel Networks Confidential
Основные требования к элементам контейнеров. • Требуется возможность копирования элемента копирующим конструктором. Созданная копия должна быть эквивалентна оригиналу. Т. е. , любая проверка на равенство должна считать копию и оригинал равными, а поведение копии не должно отличаться от поведения оригинала. Копирующий конструктор вызывается очень часто, поэтому он должен обладать хорошим быстродействием (рекомендация). • Требуется возможность присваивания элемента оператором присваивания. Контейнеры и алгоритмы используют оператор присваивания для замены старых элементов новыми. • Требуется возможность уничтожения элемента деструктором. Контейнеры уничтожают свои внутренние копии элементов при удалении этих элементов из контейнера. Следовательно, деструктор не должен быть закрытым (private). Эти три операции - копирование, присваивание, уничтожение - автоматически генерируются для любого класса. Следовательно, любой класс автоматически удовлетворяет требованиям. Nortel Networks Confidential
Дополнительные требования к элементам контейнеров. • Для некоторых методов последовательных контейнеров требуется конструктор по умолчанию. Например, можно создать непустой контейнер или увеличить количество элементов без указания значений новых элементов. Такие элементы создаются вызовом конструктора по умолчанию для соответствующего типа. • Для некоторых операций требуется определить проверку на равенство оператором ==. Такая необходимость особенно часто возникает при поиске. • Для ассоциативных контейнеров требуется, чтобы элементы поддерживали критерий сортировки. По умолчанию используется оператор <, вызываемый функтором less(). Nortel Networks Confidential
Семантика значений и ссылочная семантика. • Семантика значений - контейнеры создают внутренние копии своих элементов и возвращают эти копии. Следовательно, элементы контейнера равны, но не идентичны объектам, заносимым в контейнер. При модификации объектов, являющихся элементами контейнера, вы модифицируете копию, а не исходный объект. • Ссылочная семантика - контейнеры содержат ссылки (указатели) на объекты, являющиеся их элементами. • На практике нужны оба подхода: копии, независимые от исходных данных (семантика значений), и копии, ссылающиеся на исходные данные и изменяющиеся вместе с ними (ссылочная семантика). Nortel Networks Confidential
Семантика значений и ссылочная семантика. • Все контейнеры STL поддерживают семантику значений. Они содержат значения вставляемых объектов, а не сами объекты. Достоинства: • простота копирования элементов • при использовании ссылок часто возникают ошибки (программист должен следить за тем, чтобы ссылки не относились к несуществующим объектам, кроме того, необходимо предусмотреть обработку возможных циклических ссылок). Недостатки: • при отказе от ссылочной семантики копирование элементов может выполняться неэффективно или становится невозможным • невозможность одновременного присутствия объектов в нескольких контейнерах. Nortel Networks Confidential
Ссылочная семантика в STL. Очевидная реализация • указателей как элементовссылочной семантики основана на использовании контейнеров. Обычным указателям присущи хорошо известные недостатки. Например, объект, на который ссылается указатель, оказывается несуществующим, или операция сравнения работает не так, как предполагалось, потому что вместо объектов сравниваются указатели на них. Разумное • ссылок. решение основано на применении умных указателей с подсчетом Но такое иснользование (умного указателя, который автоматически уничтожает связанный объект при уничтожении последней ссылки на него) может вызвать немало проблем. Например, при прямом доступе к элементам возможна модификация их значений, пока они находятся в контейнере. В ассоциативном контейнере это приведет к нарушению порядка следования элементов, а это недопустимо. • auto_ptr не подходит, поскольку не удовлетворяет одному из основных требований к элементам контейнеров: после копирования или присваивания объектов класса auto_ptr оригинал и копия не эквивалентны. Более поздние версии стандарта включают в себя класс boost: : shared_ptr, который подходит для реализации ссылочной Nortel Networks Confidential семантики в STL.
Обработка ошибок. При проектировании STL главным приоритетом была максимальная производительность, а не безопасность. Проверка ошибок требует времени, поэтому в STL она практически не выполняется. • Обработка ошибок снижает быстродействие, а высокая скорость работы по прежнему остается основной целью большинства программ. • Тот, кто ставит на первое место надежность, может добиться своего при помощи интерфейсных оболочек или специальных версий STL. • В результате проверка ошибок в STL возможна, но не обязательна. В спецификации стандартной библиотеки С++ указано, что любое использование STL, нарушающее предварительные условия, приводит к непредсказуемому поведению. • Применение стандартной библиотеки STL так же чревато ошибками, как применение указателей в языке С. Найти такие ошибки иногда бывает очень • Обычно ошибки приводят к нарушению защиты памяти с неприятными побочными эффектами и даже сбоем программы. Nortel Networks Confidential трудно, особенно если нет безопасной версии STL.
Правильное использование STL. Для нормальной работы STL должны выполняться условия: • Действительность итераторов. Например, перед использованием итераторы должны инициализироваться. Следует помнить, что итераторы могут стать недействительными вследствие побочных эффектов других операций. В частности, в векторах и деках это может произойти при вставке, удалении или перемещении элементов. • Конечный итератор не связан с элементом контейнера, поэтому вызов операторов * и -> для него недопустим. В частности, это относится к возвращаемым значениям функций end() и rend() контейнерных классов. • Действительность интервалов: • итераторы, определяющие интервал, должны относиться к одному контейнеру; • второй итератор должен быть достижим в результате перебора элементов, начиная от первого. • Корректность источников: при использовании нескольких интерваловисточников второй и последующие интервалы должны содержать не меньше элементов, чем первый интервал. • Корректность приемника: количество элементов в приемном интервале Nortel Networks Confidential должно быть достаточным для перезаписи. В противном случае необходимо использовать итераторы вставки.
Пример с грубыми ошибками при использовании STL. #include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { vector<int> coll 1; vector<int> coll 2; Все ошибки происходят во время выполнения программы и приводят к непредсказуемым последствиям. // Пустая коллекция // ОШИБКА: начало находится за концом интервала vector<int>: : iterator pos = coll 1. begin(); reverse (++pos, coll 1. end()); // Вставка элементов со значениями от 1 до 9 в coll 2 for (int i = 1; i <= 9; ++i) { coll 2. push_back (i); } // ОШИБКА: перезапись несуществующих элементов copy (coll 2. begin(), coll 2. end(), // Источник coll 1. begin()); // Приемник // ОШИБКА: - перепутаны коллекции // - перепутаны begin() и end() copy (coll 1. begin(), coll 2. end(), // Источник coll 1. end()); // Приемник Nortel Networks Confidential }
Генерация исключений в STL. • Проверка логических ошибок в STL практически отсутствует, поэтому сама библиотека STL почти не генерирует исключения, связанные с логикой. • Существует только один метод, для которой в стандарте прямо указано на возможность возникновения исключения: метод at() векторов и деков (проверяемая версия оператора индексирования []). • Во всех остальных случаях стандарт требует лишь стандартных исключений типа bad_alloc при нехватке памяти или исключений, возникающих при пользовательских операциях. • Стандартная библиотека С++ предоставляет базовую гарантию безопасности исключений: возникновение исключений не приводит к утечке ресурсов или нарушению контейнерных инвариантов. • Часто требуется транзакционная безопасность: при возникновении исключения произойдет возврат к состоянию перед началом операции. (Другими словами: полное восстановление при выбрасывании исключения, т. е. операция либо завершается успешно либо не вносит изменений. ) • Лишь часть методов контейнеров гарантируют транзакционную безопасность исключений. (См. справочник. ) Nortel Networks Confidential
Генерация исключений в STL. • Проверка логических ошибок в STL практически отсутствует, поэтому сама библиотека STL почти не генерирует исключения, связанные с логикой. • Существует только один метод, для которой в стандарте прямо указано на возможность возникновения исключения: метод at() векторов и деков (проверяемая версия оператора индексирования []). Все гарантии основаны на запрете исключений в деструкторах (который в С++ должен выполняться • Во всех остальных случаяхвсегда). требует лишь стандартных стандарт исключений типа bad_alloc при нехватке памяти или исключений, Стандартная библиотека С++ соблюдает это требование; его должны возникающих при пользовательских операциях. соблюдать и прикладные программисты. • Стандартная библиотека С++ предоставляет базовую гарантию безопасности исключений: возникновение исключений не приводит к утечке ресурсов или нарушению контейнерных инвариантов. • Часто требуется транзакционная безопасность: при возникновении исключения произойдет возврат к состоянию перед началом операции. (Другими словами: полное восстановление при выбрасывании исключения, т. е. операция либо завершается успешно либо не вносит изменений. ) • Лишь часть методов контейнеров гарантируют транзакционную безопасность исключений. (См. справочник. ) Nortel Networks Confidential
Расширение STL. • Библиотека STL проектировалась с расчетом на возможность расширения практически в любом направлении. • Программист может создавать и использовать собственные контейнеры, итераторы, алгоритмы и функторы, удовлетворяющие определенным требованиям. • Кроме того, в стандартной библиотеке С++ (1998/2003) не поддерживаются некоторые полезные возможности. • Более поздние версии стандарта (TR 1 – 2005 г. ) включили в себя • новые контейнеры, основанные на хэш-таблицах. (unordered_map, unordered_set) • новые контейнеры, основанные на статических массивах. (array) • новый класс tuple (расширение идеи класса pair, «аналог» struct) • Существуют другие полезных расширения – функторы, итераторы, контейнеры и алгоритмы. Nortel Networks Confidential
Конец Nortel Networks Confidential
STD_05_STL_Algorithms.ppt