C++ осень 2012 лекция 6

48
Углубленное программирование на языке C++ Алексей Петров

Transcript of C++ осень 2012 лекция 6

Page 1: C++ осень 2012 лекция 6

Углубленное программирование

на языке C++

Алексей Петров

Page 2: C++ осень 2012 лекция 6

Лекция №6. Элементы функционального

программирования. Практическое

введение в Boost

• C++11: параметризация алгоритмов STL лямбда-функциями и

применение замыканий.

• Состав и назначение Boost.

• Примеры использования Boost: проверки времени компиляции,

характеристики типов, вариантные контейнеры, «умные» указатели,

массивы постоянной длины.

• Использование средств Boost для повышения производительности и

безопасности кода.

Page 3: C++ осень 2012 лекция 6

Лямбда-функции и замыкания

в языке C++11 (1 / 2)

Лямбда-функция — третий (наряду с указателями на функции и

классами-функторами) вариант реализации функциональных объектов

в языке C++11, обязанный своим названием λ-исчислению —

математической системе определения и применения функций, в

которой аргументом одной функции (оператора) может быть другая

функция (оператор).

Как правило, лямбда-функции являются анонимными и определяются

в точке их применения.

Возможность присваивать такие функции переменным позволяет

именовать их лямбда-выражениями.

Page 4: C++ осень 2012 лекция 6

Лямбда-функции и замыкания

в языке C++11 (2 / 2)

Лямбда-функции (лямбда-выражения) могут использоваться всюду, где

требуется передача вызываемому объекту функции

соответствующего типа, в том числе — как фактические параметры

обобщенных алгоритмов STL.

В лямбда-функции могут использоваться внешние по отношению к ней

переменные, образующие замыкание такой функции.

Многие лямбда-функции весьма просты. Например, функция

[] (int x) { return x % m == 0; }

эквивалентна функции вида

bool foo(int x) { return x % m == 0; }

Page 5: C++ осень 2012 лекция 6

Основные правила

оформления лямбда-функций

При преобразовании функции языка C++ в лямбда-функцию

необходимо учитывать, что имя функции заменяется в лямбда-функции

квадратными скобками [], а возвращаемый тип лямбда-функции:

• не определяется явно (слева);

• при анализе лямбда-функции с телом вида return expr;

автоматически выводится компилятором как decltype(expr);

• при отсутствии в теле однооператорной лямбда-функции оператора

return автоматически принимается равным void;

• в остальных случаях должен быть задан программистом при

помощи «хвостового» способа записи:

[](int x)->int { int y = x; return x – y; }

Page 6: C++ осень 2012 лекция 6

Ключевые преимущества

лямбда-функций

• Близость к точке использования — анонимные лямбда-функции

всегда определяются в месте их дальнейшего применения и

являются единственным функциональным объектом, определяемым

внутри вызова другой функции.

• Краткость — в отличие от классов-функторов немногословны, а при

наличии имени могут использоваться повторно.

• Эффективность — как и классы-функторы, могут встраиваться

компилятором в точку определения на уровне объектного кода.

• Дополнительные возможности — работа с внешними

переменными, входящими в замыкание лямбда-функции.

Page 7: C++ осень 2012 лекция 6

Применение анонимных и

именованных лямбда-функций

Для анонимных лямбда-функций:

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

Page 8: C++ осень 2012 лекция 6

Внешние переменные и замыкание

лямбда-функций (1 / 2)

Внешние по отношению к лямбда-функции автоматические

переменные, определенные в одной с ней области видимости, могут

захватываться лямбда-функцией и входить в ее замыкание. При этом

в отношении доступа к переменным действуют следующие соглашения:

• [z] — доступ по значению к одной переменной (z);

• [&z] — доступ по ссылке к одной переменной (z);

• [=] — доступ по значению ко всем автоматическим переменным;

• [&] — доступ по ссылке ко всем автоматическим переменным;

• [&, z] , [=, &z], [z, &zz] — смешанный вариант доступа.

Page 9: C++ осень 2012 лекция 6

Внешние переменные и замыкание

лямбда-функций (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;});

Page 10: C++ осень 2012 лекция 6

Библиотека Boost: общие

сведения

Boost — набор из более 80 автономных

библиотек на языке C++, задуманный в 1998 г.

Б. Давесом (Beman Dawes), Д. Абрахамсом (David Abrahams) и др.

Основными целями разработки Boost выступают:

• решение задач, выходящих за пределы возможностей стандартной

библиотеки C++ в целом и STL — в частности;

• тестирование новых библиотек-кандидатов на включение в

принимаемые и перспективные стандарты языка C++.

Преимущества и недостатки Boost связаны с активным

использованием в Boost шаблонов и техник обобщенного

программирования, что открывает перед программистами новые

возможности, но требует немалой предварительной подготовки.

Page 11: C++ осень 2012 лекция 6

Состав и назначение Boost

Page 12: C++ осень 2012 лекция 6

Почему Boost?

Сравнение с STL

Использование библиотек Boost позволяет:

• работать с высококачественным готовым исходным кодом;

• ускорить процессы разработки;

• достичь снижения количества ошибок в ПО;

• избежать «изобретения велосипеда»;

• снизить издержки на сопровождение продукта.

Page 13: C++ осень 2012 лекция 6

Boost: примеры

Продемонстрируем работу Boost на следующих примерах:

• внедрение в исходный код проверок времени компиляции —

позволяет не допустить компиляции логически или семантически

неверного кода;

• использование характеристик типов — позволяет использовать

более тонкие приемы работы с системой типов языка C++ и обойти

ряд недостатков его грамматики;

• применение вариантных контейнеров и произвольных типов —

открывает возможность создания обобщенных и универсальных

структур хранения данных;

• использование массивов постоянной длины — устраняет

накладные расходы, связанные с контейнером std::vector;

• использование «умных указателей» — повышает качество кода в

части работы с динамической памятью.

Page 14: C++ осень 2012 лекция 6

Пример 1. Проверки времени

компиляции: общие сведения

Цель. Проверки времени компиляции (англ. static assertions) призваны

предупредить случаи некорректного использования библиотек, ошибки

при передаче параметров шаблонам и пр.

Библиотека.

#include <boost/static_assert.hpp>

Состав. Проверки времени компиляции представляют собой два

макроопределения, где x — целочисленная константа, msg — строка:

BOOST_STATIC_ASSERT(x)

BOOST_STATIC_ASSERT_MSG(x, msg)

и являются статическим аналогом стандартного макроопределения

assert и пригодны для применения на уровне пространства имен,

функции или класса.

Page 15: C++ осень 2012 лекция 6

Пример 1. Проверки времени

компиляции: реализация

Реализация. На уровне программной реализации в Boost

макроопределения BOOST_STATIC_ASSERT* задействуют общий и

полностью специализированный шаблон структуры вида:

namespace boost{

template <bool>

struct STATIC_ASSERTION_FAILURE;

template <> struct

STATIC_ASSERTION_FAILURE<true>{};

}

Page 16: C++ осень 2012 лекция 6

Пример 1. Проверки времени

компиляции: использование

Использование. Например:

#include <climits>

#include <limits>

#include <boost/static_assert.hpp>

namespace my_conditions {

// проверка: длина int - не менее 32 бит

BOOST_STATIC_ASSERT(

std::numeric_limits<int>

::digits >= 32);

}

Page 17: C++ осень 2012 лекция 6

Пример 2. Характеристики типов:

общие сведения, реализация

Цель. Через набор узкоспециализированных, имеющих единый дизайн

вспомогательных классов упростить работу с атомарными

характеристиками типов (англ. type traits) в системе типов языка C++.

Библиотека.

#include <boost/type_traits.hpp>

Реализация (начало). Библиотека характеристик типов содержит

значительное количество внутренне весьма однородных классов

(структур), многие из которых открыто наследуют типам true_type или

false_type (см. далее).

Page 18: C++ осень 2012 лекция 6

Пример 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{};

Page 19: C++ осень 2012 лекция 6

Пример 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;

Page 20: C++ осень 2012 лекция 6

Пример 2. Характеристики

типов: иерархии классов

Реализация (продолжение). Такой подход позволяет строить параллельные иерархии классов, обладающих (потомки true_type) и

не обладающих (потомки false_type) искомыми свойствами.

Принадлежность параметра шаблона к соответствующей иерархии

является различительным признаком.

Page 21: C++ осень 2012 лекция 6

Пример 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;

};

Page 22: C++ осень 2012 лекция 6

Пример 2. Характеристики типов:

использование add_reference (1 / 2)

Использование (начало). Шаблон add_reference преобразует

фактический параметр типа T в const T&, а типы T& и const T&

оставляет без изменений, что позволяет, к примеру, хранить в

«стандартных» парах значения ссылок.

Page 23: C++ осень 2012 лекция 6

Пример 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) { }

};

Page 24: C++ осень 2012 лекция 6

Пример 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;

}

Page 25: C++ осень 2012 лекция 6

Пример 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);

}

Page 26: C++ осень 2012 лекция 6

Пример 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>());

}

Page 27: C++ осень 2012 лекция 6

Пример 2. Характеристики типов:

исключение вызова

деструкторов (1 / 2)

Использование (продолжение). Столь же нетрудно избежать

накладных расходов на вызов деструкторов в случаях, когда этого

допускает структура соответствующих классов:

// вызов деструкторов обязателен

template<class T>

void do_destroy_array(T* first, T* last,

const boost::false_type&)

{

while(first != last) {

first->~T();

++first;

}

}

Page 28: C++ осень 2012 лекция 6

Пример 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>());

}

Page 29: C++ осень 2012 лекция 6

Пример 3. Вариантный контейнер:

общие сведения

Цель. Предоставление программисту безопасного обобщенного

контейнера-объединения различимых типов на базе стека со

следующими возможностями:

• поддержка семантики значений, в том числе стандартных правил

разрешения типов при перегрузке;

• безопасное посещение значений с проверками времени компиляции посредством boost::apply_visitor();

• явное извлечение значений с проверками времени выполнения посредством boost::get();

• поддержка любых типов данных (POD и не-POD), отвечающих

минимальным требованиям (см. далее).

Библиотека.

#include <boost/variant.hpp>

Состав. Шаблон класса boost::variant с переменным числом

параметров, сопутствующие классы и макроопределения.

Page 30: C++ осень 2012 лекция 6

Пример 3. Вариантный контейнер:

требования к типам-параметрам

Обязательные характеристики типов-параметров шаблона

boost::variant:

• наличие конструктора копирования; • соблюдение безопасной по исключениям гарантии throw() для

деструктора;

• полнота определения к точке инстанцирования шаблона boost::variant.

Желательные характеристики типов-параметров шаблона

boost::variant:

• возможность присваивания (отсутствует для const-объектов и

ссылок!);

• наличие конструктора по умолчанию;

• возможность сравнения по отношениям «равно» и «меньше»; • поддержка работы с выходным потоком std::ostream.

Page 31: C++ осень 2012 лекция 6

Пример 3. Вариантный контейнер:

определение и обход элементов

Использование (начало). Например:

boost::variant< int, std::string > u("hello world");

std::cout << u << std::endl; // выдача: hello world

Для безопасного обхода элементов контейнера может использоваться

объект класса-посетителя (см. далее):

boost::apply_visitor(

times_two_visitor(), // объект-посетитель

v ); // контейнер

Page 32: C++ осень 2012 лекция 6

Пример 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;

}

};

Page 33: C++ осень 2012 лекция 6

Пример 3. Вариантный контейнер:

класс-посетитель (2 / 2)

Использование (окончание). Реализация класса-посетителя может

быть обобщенной:

class times_two_generic

: public boost::static_visitor<>

{

public:

template <typename T>

void operator()( T& operand ) const

{

operand += operand;

}

};

Page 34: C++ осень 2012 лекция 6

Пример 4. Произвольный тип:

общие сведения

Цель. Предоставление безопасного обобщенного класса-хранилища

единичных значений любых различимых типов, в отношении которых

не предполагается выполнение произвольных преобразований.

Основные возможности:

• копирование значений без ограничений по типам данных;

• безопасное проверяемое извлечение значения в соответствии с его

типом.

Библиотека.

#include <boost/any.hpp>

Состав. Шаблон класса boost::any, сопутствующие классы, включая

производный от std::bad_cast класс boost::bad_any_cast, и

другие объекты.

Page 35: C++ осень 2012 лекция 6

Пример 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;

};

Page 36: C++ осень 2012 лекция 6

Пример 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());

}

Page 37: C++ осень 2012 лекция 6

Пример 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;

}

}

Page 38: C++ осень 2012 лекция 6

Пример 5. Массив постоянной

длины: общие сведения

Цель. Реализация STL-совместимого контейнера-оболочки для

массивов постоянной длины, использование которого не сопряжено с

накладными расходами, присущими std::vector.

Библиотека.

#include <boost/array.hpp>

Состав. Шаблон класса boost::array, шаблон функции

boost::swap(array<T, N>&, array<T, N>&), а также шаблоны

операций-функций сравнения.

Page 39: C++ осень 2012 лекция 6

Пример 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;

Page 40: C++ осень 2012 лекция 6

Пример 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;

Page 41: C++ осень 2012 лекция 6

Пример 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

Page 42: C++ осень 2012 лекция 6

Пример 6. «Умные указатели»:

общие сведения

Цель. Снабдить программиста типом (шаблоном класса), который

ведет себя подобно стандартному указателю C++, но обладает

расширенными возможностями (поддержкой автоматического сбора

мусора и т.д.).

С теоретической точки зрения, «умный указатель» shared_ptr

является владеющим указателем с подсчетом ссылок на

управляемый им объект, который будет гарантированно удален из

динамической памяти, когда последний указывающий на него

shared_ptr будет уничтожен или сброшен в иное значение.

Библиотека.

#include <boost/shared_ptr.hpp>

Состав. Шаблон класса boost::shared_ptr и вспомогательные

артефакты.

Page 43: C++ осень 2012 лекция 6

Пример 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());

}

Page 44: C++ осень 2012 лекция 6

Пример 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[].

Page 45: C++ осень 2012 лекция 6

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;

Page 46: C++ осень 2012 лекция 6

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)); // Энергия (Н * м)

Page 47: C++ осень 2012 лекция 6

Математические библиотеки

Математическими библиотеками Boost, в частности, выступают:

• Geometry — решение геометрических задач (например, вычисление

расстояния между точками в сферической системе координат);

• Math Toolkit — работа со статистическими распределениями,

специальными математическими функциями (эллиптическими

интегралами, гиперболическими функциями, полиномами Эрмита и

пр.), бесконечными рядами и др.;

• Quaternions — поддержка алгебры кватернионов;

• Ratio — поддержка рациональных дробей;

• Meta State Machine — работа с автоматными структурами;

• и др.

Page 48: C++ осень 2012 лекция 6

Спасибо за внимание

Алексей Петров