C++ осень 2012 лекция 6
-
Upload
technopark -
Category
Documents
-
view
230 -
download
0
Transcript of C++ осень 2012 лекция 6
Углубленное программирование
на языке C++
Алексей Петров
Лекция №6. Элементы функционального
программирования. Практическое
введение в Boost
• C++11: параметризация алгоритмов STL лямбда-функциями и
применение замыканий.
• Состав и назначение Boost.
• Примеры использования Boost: проверки времени компиляции,
характеристики типов, вариантные контейнеры, «умные» указатели,
массивы постоянной длины.
• Использование средств Boost для повышения производительности и
безопасности кода.
Лямбда-функции и замыкания
в языке C++11 (1 / 2)
Лямбда-функция — третий (наряду с указателями на функции и
классами-функторами) вариант реализации функциональных объектов
в языке C++11, обязанный своим названием λ-исчислению —
математической системе определения и применения функций, в
которой аргументом одной функции (оператора) может быть другая
функция (оператор).
Как правило, лямбда-функции являются анонимными и определяются
в точке их применения.
Возможность присваивать такие функции переменным позволяет
именовать их лямбда-выражениями.
Лямбда-функции и замыкания
в языке C++11 (2 / 2)
Лямбда-функции (лямбда-выражения) могут использоваться всюду, где
требуется передача вызываемому объекту функции
соответствующего типа, в том числе — как фактические параметры
обобщенных алгоритмов STL.
В лямбда-функции могут использоваться внешние по отношению к ней
переменные, образующие замыкание такой функции.
Многие лямбда-функции весьма просты. Например, функция
[] (int x) { return x % m == 0; }
эквивалентна функции вида
bool foo(int x) { return x % m == 0; }
Основные правила
оформления лямбда-функций
При преобразовании функции языка C++ в лямбда-функцию
необходимо учитывать, что имя функции заменяется в лямбда-функции
квадратными скобками [], а возвращаемый тип лямбда-функции:
• не определяется явно (слева);
• при анализе лямбда-функции с телом вида return expr;
автоматически выводится компилятором как decltype(expr);
• при отсутствии в теле однооператорной лямбда-функции оператора
return автоматически принимается равным void;
• в остальных случаях должен быть задан программистом при
помощи «хвостового» способа записи:
[](int x)->int { int y = x; return x – y; }
Ключевые преимущества
лямбда-функций
• Близость к точке использования — анонимные лямбда-функции
всегда определяются в месте их дальнейшего применения и
являются единственным функциональным объектом, определяемым
внутри вызова другой функции.
• Краткость — в отличие от классов-функторов немногословны, а при
наличии имени могут использоваться повторно.
• Эффективность — как и классы-функторы, могут встраиваться
компилятором в точку определения на уровне объектного кода.
• Дополнительные возможности — работа с внешними
переменными, входящими в замыкание лямбда-функции.
Применение анонимных и
именованных лямбда-функций
Для анонимных лямбда-функций:
std::vector<int> v1;
std::vector<int> v2; // ...
std::transform(v1.begin(), v1.end(),
v2.begin(), [](int x) { return ++x; });
Для именованных лямбда-функций:
// тип lt10 зависит от реализации компилятора
auto lt10 = [](int x) { return x < 10; };
int cnt = std::count_if(
v1.begin(), v1.end(), lt10);
bool b = lt10(300); // b == false
Внешние переменные и замыкание
лямбда-функций (1 / 2)
Внешние по отношению к лямбда-функции автоматические
переменные, определенные в одной с ней области видимости, могут
захватываться лямбда-функцией и входить в ее замыкание. При этом
в отношении доступа к переменным действуют следующие соглашения:
• [z] — доступ по значению к одной переменной (z);
• [&z] — доступ по ссылке к одной переменной (z);
• [=] — доступ по значению ко всем автоматическим переменным;
• [&] — доступ по ссылке ко всем автоматическим переменным;
• [&, z] , [=, &z], [z, &zz] — смешанный вариант доступа.
Внешние переменные и замыкание
лямбда-функций (2 / 2)
Например:
int countN;
std::vector<double> vd; // ...
countN = std::count_if(vd.begin(), vd.end(),
[](double x) { return x >= N; });
эквивалентно
int countN = 0;
std::vector<double> vd; // ...
std::for_each(vd.begin(), vd.end(),
[&countN](double x) {countN += x >= N;});
Библиотека Boost: общие
сведения
Boost — набор из более 80 автономных
библиотек на языке C++, задуманный в 1998 г.
Б. Давесом (Beman Dawes), Д. Абрахамсом (David Abrahams) и др.
Основными целями разработки Boost выступают:
• решение задач, выходящих за пределы возможностей стандартной
библиотеки C++ в целом и STL — в частности;
• тестирование новых библиотек-кандидатов на включение в
принимаемые и перспективные стандарты языка C++.
Преимущества и недостатки Boost связаны с активным
использованием в Boost шаблонов и техник обобщенного
программирования, что открывает перед программистами новые
возможности, но требует немалой предварительной подготовки.
Состав и назначение Boost
Почему Boost?
Сравнение с STL
Использование библиотек Boost позволяет:
• работать с высококачественным готовым исходным кодом;
• ускорить процессы разработки;
• достичь снижения количества ошибок в ПО;
• избежать «изобретения велосипеда»;
• снизить издержки на сопровождение продукта.
Boost: примеры
Продемонстрируем работу Boost на следующих примерах:
• внедрение в исходный код проверок времени компиляции —
позволяет не допустить компиляции логически или семантически
неверного кода;
• использование характеристик типов — позволяет использовать
более тонкие приемы работы с системой типов языка C++ и обойти
ряд недостатков его грамматики;
• применение вариантных контейнеров и произвольных типов —
открывает возможность создания обобщенных и универсальных
структур хранения данных;
• использование массивов постоянной длины — устраняет
накладные расходы, связанные с контейнером std::vector;
• использование «умных указателей» — повышает качество кода в
части работы с динамической памятью.
Пример 1. Проверки времени
компиляции: общие сведения
Цель. Проверки времени компиляции (англ. static assertions) призваны
предупредить случаи некорректного использования библиотек, ошибки
при передаче параметров шаблонам и пр.
Библиотека.
#include <boost/static_assert.hpp>
Состав. Проверки времени компиляции представляют собой два
макроопределения, где x — целочисленная константа, msg — строка:
BOOST_STATIC_ASSERT(x)
BOOST_STATIC_ASSERT_MSG(x, msg)
и являются статическим аналогом стандартного макроопределения
assert и пригодны для применения на уровне пространства имен,
функции или класса.
Пример 1. Проверки времени
компиляции: реализация
Реализация. На уровне программной реализации в Boost
макроопределения BOOST_STATIC_ASSERT* задействуют общий и
полностью специализированный шаблон структуры вида:
namespace boost{
template <bool>
struct STATIC_ASSERTION_FAILURE;
template <> struct
STATIC_ASSERTION_FAILURE<true>{};
}
Пример 1. Проверки времени
компиляции: использование
Использование. Например:
#include <climits>
#include <limits>
#include <boost/static_assert.hpp>
namespace my_conditions {
// проверка: длина int - не менее 32 бит
BOOST_STATIC_ASSERT(
std::numeric_limits<int>
::digits >= 32);
}
Пример 2. Характеристики типов:
общие сведения, реализация
Цель. Через набор узкоспециализированных, имеющих единый дизайн
вспомогательных классов упростить работу с атомарными
характеристиками типов (англ. type traits) в системе типов языка C++.
Библиотека.
#include <boost/type_traits.hpp>
Реализация (начало). Библиотека характеристик типов содержит
значительное количество внутренне весьма однородных классов
(структур), многие из которых открыто наследуют типам true_type или
false_type (см. далее).
Пример 2. Характеристики типов:
структуры is_void, is_pointer
Реализация (продолжение). В частности:
// пример 1
template <typename T>
struct is_void : public false_type{};
template <>
struct is_void<void> : public true_type{};
// пример 2
template <typename T>
struct is_pointer: public false_type{};
template <typename T>
struct is_pointer<T*> : public true_type{};
Пример 2. Характеристики типов:
классы true_type и false_type
Реализация (продолжение). В свою очередь, структуры true_type и
false_type определены как:
template <class T, T val>
struct integral_constant
{
typedef integral_constant<T, val> type;
typedef T value_type;
static const T value = val;
};
typedef integral_constant<bool, true> true_type;
typedef integral_constant<bool, false> false_type;
Пример 2. Характеристики
типов: иерархии классов
Реализация (продолжение). Такой подход позволяет строить параллельные иерархии классов, обладающих (потомки true_type) и
не обладающих (потомки false_type) искомыми свойствами.
Принадлежность параметра шаблона к соответствующей иерархии
является различительным признаком.
Пример 2. Характеристики
типов: структура remove_extent
Реализация (окончание). Установить тип элемента массива позволяет
структура remove_extent, реализованная как:
template <typename T>
struct remove_extent
{
typedef T type;
};
template <typename T, std::size_t N>
struct remove_extent<T[N]>
{
typedef T type;
};
Пример 2. Характеристики типов:
использование add_reference (1 / 2)
Использование (начало). Шаблон add_reference преобразует
фактический параметр типа T в const T&, а типы T& и const T&
оставляет без изменений, что позволяет, к примеру, хранить в
«стандартных» парах значения ссылок.
Пример 2. Характеристики типов:
использование add_reference (2 / 2)
Использование (продолжение).
template <typename T1, typename T2>
struct pair
{
typedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
// конструктор std::pair имеет вид:
// pair(const T1&, const T2&)
pair( boost::add_reference<const T1>::type nfirst,
boost::add_reference<const T2>::type nsecond)
: first(nfirst), second(nsecond) { }
};
Пример 2. Характеристики типов:
оптимизация функций (1 / 3)
Использование (продолжение). Имея эффективную реализацию
функции для типов с конкретными характеристиками, нетрудно
должным образом специализировать ее шаблон. Например, для std::copy имеем:
// общий случай
template<typename I1, typename I2, bool b>
I2 copy_imp(I1 first, I1 last, I2 out,
const boost::integral_constant<bool, b>&)
{
while(first != last) {
*out = *first;
++out; ++first;
}
return out;
}
Пример 2. Характеристики типов:
оптимизация функций (2 / 3)
Использование (продолжение).
// оптимизированная реализация
template<typename T>
T* copy_imp(const T* first, const T* last, T* out,
const boost::true_type&)
{
memmove(out, first, (last-first)*sizeof(T));
return out + (last - first);
}
Пример 2. Характеристики типов:
оптимизация функций (3 / 3)
Использование (продолжение).
// вызывающая функция
template<typename I1, typename I2>
inline I2 copy(I1 first, I1 last, I2 out)
{
typedef typename std::iterator_traits<I1>::
value_type value_type;
return copy_imp(first, last, out,
boost::has_trivial_assign<value_type>());
}
Пример 2. Характеристики типов:
исключение вызова
деструкторов (1 / 2)
Использование (продолжение). Столь же нетрудно избежать
накладных расходов на вызов деструкторов в случаях, когда этого
допускает структура соответствующих классов:
// вызов деструкторов обязателен
template<class T>
void do_destroy_array(T* first, T* last,
const boost::false_type&)
{
while(first != last) {
first->~T();
++first;
}
}
Пример 2. Характеристики типов:
исключение вызова
деструкторов (2 / 2)
Использование (окончание). Столь же нетрудно избежать накладных
расходов на вызов деструкторов в случаях, когда этого допускает
структура соответствующих классов:
// вызов деструкторов необязателен
template<class T>
inline void do_destroy_array(T* first, T* last,
const boost::true_type&) { }
// вызывающая функция
template<class T>
inline void destroy_array(T* p1, T* p2)
{
do_destroy_array(p1, p2,
boost::has_trivial_destructor<T>());
}
Пример 3. Вариантный контейнер:
общие сведения
Цель. Предоставление программисту безопасного обобщенного
контейнера-объединения различимых типов на базе стека со
следующими возможностями:
• поддержка семантики значений, в том числе стандартных правил
разрешения типов при перегрузке;
• безопасное посещение значений с проверками времени компиляции посредством boost::apply_visitor();
• явное извлечение значений с проверками времени выполнения посредством boost::get();
• поддержка любых типов данных (POD и не-POD), отвечающих
минимальным требованиям (см. далее).
Библиотека.
#include <boost/variant.hpp>
Состав. Шаблон класса boost::variant с переменным числом
параметров, сопутствующие классы и макроопределения.
Пример 3. Вариантный контейнер:
требования к типам-параметрам
Обязательные характеристики типов-параметров шаблона
boost::variant:
• наличие конструктора копирования; • соблюдение безопасной по исключениям гарантии throw() для
деструктора;
• полнота определения к точке инстанцирования шаблона boost::variant.
Желательные характеристики типов-параметров шаблона
boost::variant:
• возможность присваивания (отсутствует для const-объектов и
ссылок!);
• наличие конструктора по умолчанию;
• возможность сравнения по отношениям «равно» и «меньше»; • поддержка работы с выходным потоком std::ostream.
Пример 3. Вариантный контейнер:
определение и обход элементов
Использование (начало). Например:
boost::variant< int, std::string > u("hello world");
std::cout << u << std::endl; // выдача: hello world
Для безопасного обхода элементов контейнера может использоваться
объект класса-посетителя (см. далее):
boost::apply_visitor(
times_two_visitor(), // объект-посетитель
v ); // контейнер
Пример 3. Вариантный контейнер:
класс-посетитель (1 / 2)
Использование (продолжение). Класс-посетитель может в таком
случае иметь реализацию вида:
class times_two_visitor
: public boost::static_visitor<>
{
public:
void operator()(int& i) const
{
i *= 2;
}
void operator()(std::string& str) const
{
str += str;
}
};
Пример 3. Вариантный контейнер:
класс-посетитель (2 / 2)
Использование (окончание). Реализация класса-посетителя может
быть обобщенной:
class times_two_generic
: public boost::static_visitor<>
{
public:
template <typename T>
void operator()( T& operand ) const
{
operand += operand;
}
};
Пример 4. Произвольный тип:
общие сведения
Цель. Предоставление безопасного обобщенного класса-хранилища
единичных значений любых различимых типов, в отношении которых
не предполагается выполнение произвольных преобразований.
Основные возможности:
• копирование значений без ограничений по типам данных;
• безопасное проверяемое извлечение значения в соответствии с его
типом.
Библиотека.
#include <boost/any.hpp>
Состав. Шаблон класса boost::any, сопутствующие классы, включая
производный от std::bad_cast класс boost::bad_any_cast, и
другие объекты.
Пример 4. Произвольный тип:
класс any
Реализация.
class any {
public:
// construct/copy/destruct
any();
any(const any&);
template<typename ValueType>any(const ValueType&);
any& operator=(const any&);
template<typename ValueType>
any& operator=(const ValueType&);
~any();
// modifiers
any& swap(any&);
// queries
bool empty() const;
const std::type_info& type() const;
};
Пример 4. Произвольный тип:
работа со стандартными списками
Использование (начало). Двусвязный список значений произвольных
типов может формироваться и использоваться так:
typedef std::list<boost::any> many;
void append_string(
many& values, const std::string& value) {
values.push_back(value);
}
void append_any(
many& values, const boost::any& value) {
values.push_back(value);
}
void append_nothing(many& values)
{
values.push_back(boost::any());
}
Пример 4. Произвольный тип:
проверка типов значений
Использование (окончание).
bool is_int(const boost::any& operand) {
return operand.type() == typeid(int);
}
bool is_char_ptr(const boost::any& operand)
{
try {
boost::any_cast<const char *>(operand);
return true;
}
catch(const boost::bad_any_cast&) {
return false;
}
}
Пример 5. Массив постоянной
длины: общие сведения
Цель. Реализация STL-совместимого контейнера-оболочки для
массивов постоянной длины, использование которого не сопряжено с
накладными расходами, присущими std::vector.
Библиотека.
#include <boost/array.hpp>
Состав. Шаблон класса boost::array, шаблон функции
boost::swap(array<T, N>&, array<T, N>&), а также шаблоны
операций-функций сравнения.
Пример 5. Массив постоянной
длины: класс array (1 / 3)
Реализация (начало).
template<typename T, std::size_t N>
class array {
public:
// типы
typedef T value_type;
typedef T* iterator;
typedef const T* const_iterator;
typedef std::reverse_iterator<iterator>
reverse_iterator;
typedef std::reverse_iterator<const_iterator>
const_reverse_iterator;
typedef T& reference;
typedef const T& const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
Пример 5. Массив постоянной
длины: класс array (2 / 3)
Реализация (продолжение). // template<typename T, std::size_t N> class array {
// статические константы
static const size_type static_size = N;
// операции присваивания
template<typename U>
array& operator=(const array<U, N>&);
// поддержка прямых и обратных итераторов
iterator begin();
const_iterator begin() const;
iterator end();
const_iterator end() const;
reverse_iterator rbegin();
const_reverse_iterator rbegin() const;
reverse_iterator rend();
const_reverse_iterator rend() const;
Пример 5. Массив постоянной
длины: класс array (3 / 3)
Реализация (окончание). // template<typename T, std::size_t N> class array {
// емкость
size_type size();
bool empty();
size_type max_size();
// доступ к элементам
reference operator[](size_type);
const_reference operator[](size_type) const;
reference at(size_type);
const_reference at(size_type) const;
reference front();
const_reference front() const;
reference back();
const_reference back() const;
const T* data() const; /* swap, assign, … */
}; // class array
Пример 6. «Умные указатели»:
общие сведения
Цель. Снабдить программиста типом (шаблоном класса), который
ведет себя подобно стандартному указателю C++, но обладает
расширенными возможностями (поддержкой автоматического сбора
мусора и т.д.).
С теоретической точки зрения, «умный указатель» shared_ptr
является владеющим указателем с подсчетом ссылок на
управляемый им объект, который будет гарантированно удален из
динамической памяти, когда последний указывающий на него
shared_ptr будет уничтожен или сброшен в иное значение.
Библиотека.
#include <boost/shared_ptr.hpp>
Состав. Шаблон класса boost::shared_ptr и вспомогательные
артефакты.
Пример 6. «Умные указатели»:
техника применения
Использование. Простейшей рекомендацией по использованию «умных указателей» shared_ptr, практически устраняющей
возможность утечек памяти, является использование именованных указателей для каждой операции new:
shared_ptr<T> p(new Y);
Такая техника естественным образом сокращает количество явных
операций delete и интенсивность использования пар try/catch.
Не рекомендуется:
void f(shared_ptr<int>, int);
int g();
void bad() {
f(shared_ptr<int>(new int(2)), g());
}
Пример 6. «Умные указатели»:
допустимые преобразования и
аналоги
Преобразования. В отсутствие каких-либо значимых ограничений на
тип параметра shared_ptr<T> допускается:
• неявное преобразование shared_ptr<T> к shared_ptr<U>, если
T* может быть неявно преобразовано в U*, к примеру, возможно:
• неявное преобразование shared_ptr<T> к
shared_ptr<T const>;
• неявное преобразование shared_ptr<T> к shared_ptr<U>, если U
есть достижимый из T базовый класс;
• неявное преобразование shared_ptr<T> к shared_ptr<void>.
Аналоги. Функциональным аналогом shared_ptr для массивов
является shared_array, отвечающий за автоматическое удаление
объектов, размещенных посредством операции new[].
Boost: что еще? (1 / 2)
Boost Interval Container Library (ICL) — библиотека интервальных
контейнеров с поддержкой множеств и отображений интервалов.
Например:
// работа с интервальным множеством
interval_set<int> mySet;
mySet.insert(42);
bool has_answer = contains(mySet, 42);
Boost.Tribool — поддержка тернарной логики «да — нет — возможно».
Например:
tribool b(true);
b = false;
b = indeterminate;
Boost: что еще? (2 / 2)
Boost.Units — библиотека поддержки анализа размерностей (единиц
измерения) операндов вычислительных операций. Задача анализа
рассматривается как обобщенная задача метапрограммирования
времени компиляции. Например:
quantity<force> F(2.0 * newton); // Сила (2 Н)
quantity<length> dx(2.0 * meter); // Расстояние(2 м)
quantity<energy> E(work(F, dx)); // Энергия (Н * м)
Математические библиотеки
Математическими библиотеками Boost, в частности, выступают:
• Geometry — решение геометрических задач (например, вычисление
расстояния между точками в сферической системе координат);
• Math Toolkit — работа со статистическими распределениями,
специальными математическими функциями (эллиптическими
интегралами, гиперболическими функциями, полиномами Эрмита и
пр.), бесконечными рядами и др.;
• Quaternions — поддержка алгебры кватернионов;
• Ratio — поддержка рациональных дробей;
• Meta State Machine — работа с автоматными структурами;
• и др.
Спасибо за внимание
Алексей Петров