LXF147:tut7
|
|
|
Содержание |
Boost: Набор библиотек С++
- Их использование существенно облегчает написание и чтение кода, считает Семен Есилевский.
- Метамодернизм в позднем творчестве В.Г. Сорокина
- ЛитРПГ - последняя отрыжка постмодерна
- "Ричард III и семиотика"
- 3D-визуализация обложки Ridero создаем обложку книги при работе над самиздатом.
- Архитектура метамодерна - говоря о современном искусстве, невозможно не поговорить об архитектуре. В данной статье будет отмечено несколько интересных принципов, характерных для построек "новой волны", столь притягательных и скандальных.
- Литература
- Метамодерн
- Рокер-Прометей против изначального зла в «Песне про советскую милицию» Вени Дркина, Автор: Нина Ищенко, к.ф.н, член Союза Писателей ЛНР - перепубликация из журнала "Топос".
- Как избавиться от комаров? Лучшие типы ловушек.
- Что делать если роблокс вылетает на windows
- Что делать, если ребенок смотрит порно?
- Почему собака прыгает на людей при встрече?
- Какое масло лить в Задний дифференциал (мост) Visco diff 38434AA050
- О чем может рассказать хвост вашей кошки?
- Верветки
- Отчетность бюджетных учреждений при закупках по Закону № 223-ФЗ
- Срок исковой давности как правильно рассчитать
- Дмитрий Патрушев минсельхоз будет ли преемником Путина
- Кто такой Владислав Поздняков? Что такое "Мужское Государство" и почему его признали экстремистским в России?
- Как правильно выбрать машинное масло в Димитровграде?
- Как стать богатым и знаменитым в России?
- Почему фильм "Пипец" (Kick-Ass) стал популярен по всему миру?
- Как стать мудрецом?
- Как правильно установить FreeBSD
- Как стать таким как Путин?
- Где лучше жить - в Димитровграде или в Ульяновске?
- Почему город Димитровград так называется?
- Что такое метамодерн?
- ВАЖНО! Временное ограничение движения автотранспортных средств в Димитровграде
- Тарифы на электроэнергию для майнеров предложено повысить
Вступление
Ни для кого не секрет, что язык С++, оставаясь самым мощным компилируемым языком общего назначения, выглядит довольно неудобным по современным меркам. Синтаксис многих конструкций очень запутан, а простые вещи зачастую реализуются неоправданно сложно. Большинство этих проблем ликвидирует набор библиотек Boost (http://www.boost.org/), призванный, как следует из названия, кардинально повысить продуктивность программирования на С++. Boost имеет «полуофициальный» статус, поскольку многие его разработчики являются членами комитета стандартов С++, а некоторые из входящих в Boost библиотек уже включены в новый стандарт С++0х. Кроме того, лицензия Boost Software License позволяет свободно и бесплатно использовать Boost как в открытых, так и в коммерческих проектах.
Библиотеки Boost – чрезвычайно мощные, используют новейшие технологии программирования (такие как шаблонное метапрограммирование) и тщательно тестируются, однако назвать их дружественными к программисту трудно. Многие библиотеки имеют излишне запутанный синтаксис при явно недостаточной документации. Особенно это относится к самым мощным, но и самым сложным библиотекам, таким как генератор парсеров Spirit. В то же время в повседневной работе чаще всего нужны относительно небольшие, простые и практичные библиотеки из набора Boost. Некоторые из этих совсем не страшных и очень полезных библиотек рассмотрены в этой статье. Все они являются «заголовочными» [header-only], поэтому не нужно добавлять к программе что-либо на стадии компоновки.
Эта статья ориентирована на читателей, имеющих некоторый опыт программирования на С++ и хотя бы поверхностно знакомых со стандартной библиотекой и контейнерами STL.
Из чисел в строки и наоборот
- Boost.lexical_cast
Рутинная задача преобразования числа в строку или строки в число решается стандартными средствами С++ на удивление неуклюже. Можно использовать функции Си atoi() и itoa(), но они работают со строками с стиле Си, а не с объектами типа string. Можно использовать класс stringstream:
#include <sstream> ... double v = 3.14; string str; // Преобразовать число в строку stringstream ss; ss << v; str = ss.str(); // Преобразовать строку в число str = “100.3”; ss.str(ss); ss >> v;
Если нужно провести много преобразований, то использование объекта stringstream оправданно, но в обычном сценарии вводить промежуточную переменную для конвертации одного числа неудобно. Именно для таких случаев и создана шаблонная функция lexical_cast:
#include <boost/lexical_cast.hpp> … double v = 3.14; string str; // Преобразовать число в строку str = boost::lexical_cast<string>(v); // Преобразовать строку в число str = “100.3”; v = boost::lexical_cast<double>(str);
Как говорится, проще не бывает.
Заполнение контейнеров
- Boost.assign
Стандартные контейнеры STL имеют один досадный недостаток: чтобы наполнить их элементами, всегда приходится либо писать цикл, либо вручную вызывать push_back:
vector<int> values; // Заполняем вектор квадратами индексов for(int i=1; i<=5; ++i){ values.push_back(i*i); } // Список слов известной фразы Гамлета list<string> words; words.push_back(“to”); words.push_back(“be”); words.push_back(“or”); words.push_back(“not”); words.push_back(“to”); words.push_back(“be”);
Ничего сложного, но избыточность этого кода видна невооруженным глазом. С помощью Boost.assign можно избавиться от этой проблемы, используя перегруженный оператор “,”:
#include <boost/assign.hpp> ... values += 1,4,9,16,25; words += ”to”,”be”,”or”,”not”,”to”,”be”;
С ассоциативными контейнерами удобно использовать перегруженный оператор “()” и функцию insert:
map<string,int> months; insert( months ) ( ”january”, 31 )( ”february”, 28 ) ( ”march”, 31 )( ”april”, 30 ) ( ”may”, 31 )( ”june”, 30 ) ( ”july”, 31 )( ”august”, 31 ) ( ”september”, 30 )( ”october”, 31 ) ( ”november”, 30 )( ”december”, 31 );
Наконец, можно инициализировать любой контейнер сразу при его объявлении с помощью функции list_of:
vector<int> vals = list_of(1)(4)(9)(16)(25);
Для ассоциативных контейнеров предусмотрен особый вариант map_list_of:
map<int,int> next = map_list_of(1,2)(2,3)(3,4)(4,5)(5,6);
В Boost.assign есть множество других полезных возможностей – например, заполнение с повторами и заполнение без избыточного копирования данных.
Имитация конструкции foreach
- Boost.foreach
Предположим, у вас есть какой-то контейнер – например, список строк типа std::list<string>; и вы просто хотите вывести этот список на экран. «Штатное» решение выглядит так:
#include <boost/assign.hpp> using namespace std; list<string> lst; ... // Выводим список list<string>::iterator it; for(it=lst.begin(); it!=lst.end(); it++){ cout << *it << endl; }
Вроде бы все хорошо, но для такой тривиальной операции приходится писать слишком много служебного кода. Итераторы – очень мощное средство, но в данном случае их применение – стрельба из пушки по воробьям. В нашем примере итератор нужен всего лишь чтобы получить значение текущего элемента контейнера. Во многих языках, таких как, например, C# или Java, есть конструкция foreach, предназначенная именно для легкого итерирования по любой последовательности или контейнеру. Макрос BOOST_FOREACH предназначен для ее эмуляции в С++:
// Выводим список BOOST_FOREACH(string s, lst){ cout << s << endl; }
Согласитесь, выглядит намного понятнее и проще. Если нужно не просто читать элементы списка, но и модифицировать их, то достаточно объявить переменную цикла как ссылку:
// Список чисел list<double> v = list_of(1)(2)(3)(4)(5); // Превращаем его в список квадратов чисел BOOST_FOREACH(double& d, v){ d = d*d; }
Макрос BOOST_FOREACH реализован так, что никогда не выделяет память динамически, а получающийся код по эффективности не уступает написанному вручную с помощью итераторов. Работает он с любыми контейнерами STL, обычными массивами, строками в стиле Си или объектами string. Можно также передать std::pair, содержащий пару любых итераторов, и макрос «пробежит» диапазон между ними. В теле цикла можно использовать обычные операторы return, continue и break:
std::deque<int> deque_int( /*...*/ ); int i = 0; BOOST_FOREACH( i, deque_int ) { if( i == 0 ) return; if( i == 1 ) continue; if( i == 2 ) break; }
Циклы могут быть вложенными на любую глубину. Можно итерировать и в обратном порядке, используя аналогичный макрос BOOST_REVERSE_FOREACH.
Начав использовать BOOST_FOREACH, отказаться от него очень сложно: количество служебного кода зачастую уменьшается в разы, а его читаемость существенно возрастает.
Функции обратного вызова
- Boost.function и Boost.bind
Функции обратного вызова [callbacks] – очень распространенная идиома программирования. Особенно часто они используются при программировании GUI-приложений для отклика на события. Как правило, их реализуют с помощью указателей на функции. Одна беда – синтаксис указателей на функции в С++ просто устрашающий! Для иллюстрации создадим заведомо бесполезный класс, выполняющий сложение и вычитание:
class Math { public: double do_add(double a, double b){ return a+b; } double do_sub(double a, double b){ return a-b; } };
Посмотрим, как можно вызвать эти методы с помощью указателей на функцию:
int main(int argc, char* argv[]){ Math m; double (Math::*ptr_add)(double, double) = &Math::do_add; double (Math::*ptr_sub)(double, double) = &Math::do_sub; cout << ”Сумма: ” << (m.*ptr_add)(200.0,100.0) << endl; cout << ”Разность: ” << (m.*ptr_sub)(200.0,100.0) << endl; }
И как вам такой кошмарный синтаксис? ptr_add не привязан к конкретному экземпляру класса Math, и при вызове приходится явно писать m.*ptr_add. Выглядит так, как будто у объекта есть метод ptr_add; но его нет, и смысл конструкции совершенно иной. Предположим, теперь мы хотим создать функцию, которой будут передаваться два числа и указатель на метод, который к ним надо применить (т. е. собственно классический вариант обратного вызова). Понять, как это записать, очень сложно, и я даже не буду приводить этот код. Одним словом, синтаксис ужасен, а в более сложных случаях он становится вообще практически нечитабельным. На помощь приходит тандем Boost.function и Boost.bind. С их помощью наш пример становится значительно проще для понимания:
#include <boost/bind.hpp> #include <boost/function.hpp> double do_operation(boost::function<double(double, double)> func ,double a, double b){ return func(a,b); } int main(int argc, char* argv[]){ Math m; boost::function<double(double, double)> = boost::bind(&Math::do_add,&m,_1,_2); boost::function<double(double, double)> ptr_sub = boost::bind(&Math::do_sub,&m,_1,_2); cout << ”Сумма: ” << ptr_add(200.0,100.0) << endl; cout << ”Разность: ” << ptr_sub(200.0,100.0) << endl; }
Конструкция boost::function<double(double, double)> func читается естественным образом – понятно, что func – это функция с сигнатурой double(double, double). Этот же тип имеют и переменные ptr_add и ptr_sub – сразу ясно, что функция do_operation готова с ними работать.
Смысл конструкции boost::bind(&Math::do_add,&m,_1,_2) понять немного сложнее. bind связывает функцию и ее аргументы в единый «вызываемый» объект, обращаться с которым можно как с обычной функцией. В нашем случае мы связываем метод Math::do_add с экземпляром нашего класса m и «магическими» переменными _1 и _2. При вызове созданного объекта _1 автоматически заменяется первым фактическим аргументом, _2 – вторым и т. д. Это так называемые заполнители [placeholders] для аргументов. Наконец, мы просто вызываем ptr_add и ptr_sub как обычные функции, поскольку они уже связаны с нужным экземпляром класса Math.
Научившись работать с Boost.function и Boost.bind, можно навсегда забыть о кошмарном синтаксисе указателей на функции.
Сигналы и слоты
- Boost.signals2
Если вы работали с библиотекой Qt, то знаете, насколько удобной во многих случаях является концепция сигналов и слотов. Сигналы – это, по сути, обобщение идеи функций обратного вызова. Сигнал может быть соединен со множеством слотов, и все они будут вызываться при активации сигнала. Соединение со слотом создается вручную, но разрывается автоматически, когда разрушается вызывающий либо обрабатывающий сигнал объект. Это очень важное свойство – оно позволяет не заботиться о том, что случайно будет вызван слот несуществующего объекта и вся программа «рухнет». Обычные функции обратного вызова никаких гарантий на этот случай не дают.
В Boost есть две очень похожие библиотеки, реализующие сигналы – signals и signals2. Последняя удобнее и современнее, поскольку является полностью заголовочной и позволяет посылать сигналы между разными нитями в многопоточной программе.
Для примера напишем программу, печатающую результат четырех арифметических действий с числами:
#include <boost/signals2.hpp> void mul(float x, float y) { cout << x * y << endl; } void div(float x, float y) { cout << x / y << endl; } void add(float x, float y) { cout << x + y << endl; } void sub(float x, float y) { cout << x - y << endl; } int main(int argc, char* argv[]){ boost::signals2::signal<void (float, float)> sig; sig.connect(&add); sig.connect(&sub); sig.connect(&mul); sig.connect(&div); sig(10,5); }
Мы создали сигнал с нужной нашим функциям сигнатурой void (float, float), соединили его со всеми четырьмя функциями методом connect() и активировали, передав параметры 10 и 5. В результате вызываются все функции поочередно.
Сигналы можно соединять не только с функциями, но и с любыми вызываемыми объектами (для которых определен оператор “()”), в том числе с теми, которые создает boost::bind. Например, в нашем примере с функциями обратного вызова можно было бы написать
boost::signals2::signal<double (double, double)> sig; sig.connect( boost::bind(&Math::do_sub,&m,_1,_2) ); cout << *sig(200,100) << endl;
При этом вызывается нужный метод, но значение возвращается в виде указателя, и его нужно разыменовать.
Если вызывается несколько сигналов, которые возвращают значения, то результат такого вызова неоднозначен. Что при этом будет возвращено, решает пользователь с помощью так-называемых «комбинаторов» [combiners] – специальных объектов, которые аккумулируют значения, возвращенные всеми слотами, и обрабатывают их. Однако это уже «высший пилотаж». Если же игнорировать возвращаемые значения слотов, то пользоваться сигналами чрезвычайно просто.
Гетерогенные контейнеры
- Boost.variant и Boost.any
Один из хрестоматийных вопросов, постоянно задаваемых на тематических форумах – как создать гетерогенный контейнер в С++? С++ – строго типизированный язык, и в массивах или контейнерах STL можно хранить значения только какого-то одного определенного типа. В то же время часто возникает необходимость создать гетерогенный контейнер, содержащий, скажем, одновременно числа и строки. Сделать это силами стандартной библиотеки можно, но решение будет громоздким и небезопасным, т. к. придется использовать «тяжелую артиллерию» вроде указателей типа *void.
В Boost есть стредства, позволяющие создать удобные и безопасные гетерогенные контейнеры со строгой проверкой типов. Начнем со случая, когда мы четко знаем, что будем хранить либо строки, либо целые числа, либо числа с плавающей точкой:
#include <boost/variant.hpp> using namespace std; … typedef boost::variant<string, double, int> data_t; vector<data_t> data; data.push_back(123); data.push_back(3.14); data.push_back(“Hello!”);
Мы перечисляем все нужные типы как параметры шаблонного типа variant, после чего можем использовать его как тип для нашего контейнера. В контейнер теперь можно добавлять данные всех перечисленных типов. Чтобы прочитать данные, нужно либо точно знать тип текущего элемента (что бывает редко), либо действовать методом проб и ошибок. Например, так можно вывести все данные из нашего контейнера вместе с их типом:
BOOST_FOREACH(data_t& item, data){ int* ptr1 = boost::get<int>(&item); if(ptr) cout << ”Это целое число:” << *ptr1 << endl; double* ptr2 = boost::get<double>(&item); if(ptr2) cout << ”Это число c плавающей точкой:” << *ptr2 << endl; string* ptr3 = boost::get<string>(&item); if(ptr3) cout << ”Это строка:” << *ptr3 << endl; }
Шаблонная функция boost::get<> пытается получить значение заданного типа из переменной типа variant (передается ее адрес). Если это удается, она возвращает указатель нужного типа на это значение, а если не удается, то NULL. Перебирая все варианты, можно определить и тип элемента, и его значение. Есть и другой вариант этой функции, который принимает не адрес, а саму переменную, и возвращает не указатель, а само значение. Если тип не совпадает, то генерируется исключение типа bad_get:
try { int val = boost::get<int>(item); } catch(const boost::bad_get&){ cout << ”Это не целое число!” << endl; }
Другой сценарий использования гетерогенного контейнера возникает, когда нужно хранить значения действительно любого типа либо когда вариантов типов очень много. В таком случае на помощь приходит Boost.any:
boost::any data; // Можно хранить что угодно! ... // Пытаемся извлечь строку try{ cout << any_cast<string>(data) << endl; } catch(const boost::bad_any_cast&) { cout << ”Это не строка!” << endl; }
Функция any_cast ведет себя точно так же, как и boost::get, и тоже существует в двух вариантах, возвращающих указатель либо само значение.
Выводы
Библиотеки Boost вполне оправдывают свое название – они позволяют повысить продуктивность программирования на С++ и создают удобства, невиданные в рамках базового языка и стандартной библиотеки. Особенно хорошо с этой задачей справляются «маленькие» библиотеки вроде Boost.foreach и Boost.assign – простые, легкие в освоении и имеющие удобный синтаксис. Они заполняют пробелы стандартной библиотеки и исправляют недостатки синтаксиса самого языка. Ярким примером являются Boost.bind и Boost.function, позволяющие забыть об указателях на функции, их ограничениях и ужасном синтаксисе. Однако не все рассмотренные в этой статье библиотеки являются «маленькими». Например, Boost.signals2 имеет свои «темные углы», а синтаксис комбинаторов способен повергнуть новичка в уныние. В этом особенность Boost – отход от простых моделей использования часто приводит пользователя в плохо документированные «дебри». Тем не менее, Boost – обязательная часть арсенала современного программиста на С++, который хочет работать действительно эффективно, а не бороться постоянно с синтаксическими тонкостями и излишней низкоуровневостью этого языка.
- Метамодернизм в позднем творчестве В.Г. Сорокина
- ЛитРПГ - последняя отрыжка постмодерна
- "Ричард III и семиотика"
- 3D-визуализация обложки Ridero создаем обложку книги при работе над самиздатом.
- Архитектура метамодерна - говоря о современном искусстве, невозможно не поговорить об архитектуре. В данной статье будет отмечено несколько интересных принципов, характерных для построек "новой волны", столь притягательных и скандальных.
- Литература
- Метамодерн
- Рокер-Прометей против изначального зла в «Песне про советскую милицию» Вени Дркина, Автор: Нина Ищенко, к.ф.н, член Союза Писателей ЛНР - перепубликация из журнала "Топос".
- Как избавиться от комаров? Лучшие типы ловушек.
- Что делать если роблокс вылетает на windows
- Что делать, если ребенок смотрит порно?
- Почему собака прыгает на людей при встрече?
- Какое масло лить в Задний дифференциал (мост) Visco diff 38434AA050
- О чем может рассказать хвост вашей кошки?
- Верветки
- Отчетность бюджетных учреждений при закупках по Закону № 223-ФЗ
- Срок исковой давности как правильно рассчитать
- Дмитрий Патрушев минсельхоз будет ли преемником Путина
- Кто такой Владислав Поздняков? Что такое "Мужское Государство" и почему его признали экстремистским в России?
- Как правильно выбрать машинное масло в Димитровграде?
- Как стать богатым и знаменитым в России?
- Почему фильм "Пипец" (Kick-Ass) стал популярен по всему миру?
- Как стать мудрецом?
- Как правильно установить FreeBSD
- Как стать таким как Путин?
- Где лучше жить - в Димитровграде или в Ульяновске?
- Почему город Димитровград так называется?
- Что такое метамодерн?
- ВАЖНО! Временное ограничение движения автотранспортных средств в Димитровграде
- Тарифы на электроэнергию для майнеров предложено повысить