С++ std::functional
Functional заголовочный файл в стандартной библиотеке языка программирования C++, предоставляющий набор шаблонов классов для работы с функциональными объектами, а также набор вспомогательных классов для их использования в алгоритмах стандартной библиотеки.
Функциональный объект
может быть использован следующим образом:
У использования функциональных объектов есть ряд преимуществ перед использованием функций, а именно:
Предикаты
Обёртки функций
std::function
Начиная со стандарта C++11 шаблонный класс std::function является полиморфной обёрткой функций для общего использования. Объекты класса std::function могут хранить, копировать и вызывать произвольные вызываемые объекты — функции, лямбда-выражения, выражения связывания и другие функциональные объекты. Говоря в общем, в любом месте, где необходимо использовать указатель на функцию для её отложенного вызова, или для создания функции обратного вызова, вместо него может быть использован std::function, который предоставляет пользователю большую гибкость в реализации.
Определение класса std::function
Оператор приведения function к булевскому типу возвращает true, когда у класса есть целевой объект.
std::bad_functional_call
Исключение типа bad_functional_call будет брошено при попытке вызова обёртки функции function::operator(), если у этой обёртки отсутствует целевой объект. bad_functional_call наследуется от std::exception, и у него доступен виртуальный метод what() для получения текста ошибки. Пример использования:
std::mem_fn
Шаблонная функция std::mem_fn создаёт объект-обёртку вокруг указателей на члены класса. Этот объект может хранить, копировать и вызывать член класса по указателю. В качестве указателя могут также использоваться ссылки и умные указатели.
Связыватели
std::bind
Шаблонная функция std::bind называется связывателем и предоставляет поддержку частичного применения функций. Она привязывает некоторые аргументы к функциональному объекту, создавая новый функциональный объект. То есть вызов связывателя эквивалентен вызову функционального объекта с некоторыми определёнными параметрами. Передавать связывателю можно или непосредственно значения аргументов, или специальные имена, определенные в пространстве имен std::placeholders, которые указывают связывателю на то, что данный аргумент не будет связан, и определяют порядок аргументов у возвращаемого функционального объекта.
Определение функции std::bind
std::placeholders
Для получения целого числа k из заполнителя _K предусмотрен вспомогательный шаблонный класс std::is_placeholder. При передаче ему заполнителя, как параметра шаблона, есть возможность получить целое число при обращении к его полю value. К примеру, is_placeholder ::value вернёт 3.
Функциональные объекты
Для создания объектов reference_wrapper предоставлены вспомогательные функции ref и cref, определённые следующим образом:
Русские Блоги
Используйте std :: function как параметр функции
1. О std :: function ()
В эпоху языка C мы можем использовать указатели функций для передачи функции в качестве параметра, чтобы мы могли реализовать механизм функции обратного вызова. После C ++ 11 класс шаблона std :: function был представлен в стандартной библиотеке. Этот шаблон обобщает концепцию указателей на функции.
Указатель функции может указывать только на одну функцию, а объект std :: function может представлять любой объект, который может быть вызван, например, любой объект, который может быть вызван как функция.
Когда вы создаете указатель функции, вы должны определить сигнатуру функции (характеризующую входные параметры функции, возвращаемое значение и другую информацию); аналогично, когда вы создаете std :: function, когда объект, необходимо также указать сигнатуру функции вызываемого объекта, который он представляет. Этого можно добиться с помощью параметра шаблона std :: function.
Например, если вы хотите определить функцию объекта std :: function, этот объект может представлять любой вызываемый объект со следующей сигнатурой функции,
Вы можете написать так,
2. Используйте std :: function как параметр функции.
2.1 Передача параметров по значению
Обратитесь к следующему фрагменту кода, чтобы реализовать механизм регистрации функций обратного вызова.
Входной параметр std :: function представляет собой объект класса шаблона, который может быть инициализирован вызываемым объектом, сигнатура функции которого void (); приведенная выше реализация представляет собой вызов по значению. Давайте посмотрим на процесс его вызова,
2.2 Передача параметров на основе ссылки
Конечно, мы также можем реализовать эту функцию регистрации следующим образом. Входные параметры передаются по константной ссылке. Ссылка здесь должна быть константной. Это потому, что временный объект std :: function () создается там, где вызывается функция registerCallBack. Rvalue, иначе компилятор сообщит об ошибке.
Разница между ними заключается в том, как использовать этот входной параметр внутри функции registerCallBack. Если вы просто вызываете класс std :: func (), то с двумя типами проблем нет, и может быть более эффективно использовать ссылки. ; если функция регистрации This std :: func () должна быть сохранена внутри и использоваться для будущего использования. Тогда нет проблем с прямым сохранением метода A, метод B должен сделать копию, в противном случае в методе B, когда временный объект уничтожен, возможно, проблема с зависшим эталоном.
2.3 сохранение объекта std :: function в режиме передачи по значению
Если мы хотим сохранить переданный объект функции внутри функции registerCallBack, мы можем использовать операцию передачи std :: move, которая более эффективна.
3. Функции-члены класса как параметры функции
Функции-члены класса по умолчанию будут иметь скрытый указатель this, поэтому, в отличие от обычных функций, его можно напрямую использовать в качестве параметра.
3.1 Используйте std :: bind () и std :: function для достижения
Другой класс classB должен зарегистрировать собственную функцию-член как функцию обратного вызова для classA, здесь вы можете использовать функцию std :: bind для достижения,
В Эффективном современном C ++ есть раздел, в котором объясняется, что метод std :: bind громоздок и иногда имеет некоторые ограничения, поэтому после введения лямбда-выражений вы можете использовать лямбда-выражения для замены std :: bind для реализации регистрации функций обратного вызова.
3.2 Реализация с использованием лямбда-выражений
Использование лямбда-выражений может упростить этот процесс. Обратитесь к следующему фрагменту кода, classB регистрирует функцию-член как функцию обратного вызова для classA, а classA сохраняет эту функцию обратного вызова (объект std :: function) в переменную-член для дальнейшего использования,
Std function c что это
Статья об использовании std::function
Поводом для написания данной статьи стало желание систематизировать всё то, чему я научился в процессе кодинга лаб по таким предметам, как Численные методы; Уравнения математической физики; Методы оптимизации. Надеюсь эта статья будет полезна не только людям, которые учатся в НГТУ на факультете ФПМИ, но и широкому кругу читателей.
В Си/C++ имеется возможность передавать функцию в качестве аргумента функции как указатель на функцию. Вы наверняка знаете этот страшный синтаксис:
Причём, в отличие от Си, в C++ таким образом можно описывать любой функциональный объект, то есть объект, допускающий вызов операции () :
Аналогичным образом можно использовать лямбды:
Таким образом, можно передавать функции в качестве аргументов функций.
Численное вычисление производной
Но писать для каждой функции другую функцию, вычисляющую её производную непрактично! Поэтому такую проблему можно решить с помощью std::function :
Данная функция получает функцию, и возвращает функцию, которая считает её производную. Пример использования:
А далее представлен код для вычисления первой и второй производных функций, взятый из курсовой по УМФ, смело используйте его в своих проектах.
Пример использования std::bind
Автоматический расчет правой части
В таком предмете, как УМФ требуется решить следующее дифференциальное уравнение:
где u = u(x, y, t) является неизвестной функцией. Для численного решения используется Метод Конечных Элементов.
Для декартовых координат это уравнение раскладывается в lambda*(d^2 u/dx^2 + d^2 u/dy^2) + gamma * u + sigma * du/dt = f
Для того, чтобы тестировать нашу программу на широком спектре функций, можно написать функцию, которая будет автоматически рассчитывать эту правую часть на основе вышеописанных функций для вычисления производных:
Код взят опять же из репозитория по УМФ.
Это избавляет от лишней рутины ручного вычисления производных для функций при тестировании, автоматизируя этот процесс, также снижается вероятность ошибиться.
Обертка для подсчета вызовов функции
Это можно сделать не вмешиваясь в код этого метода, явным образом выставляя count++ при каждом вызове функции, послав вместо функции обёртку над ней:
У нас стоит задача замерить время работы какого-то кода. Можно поступить следующим образом:
Но это решение плохо тем, что нам постоянно надо копировать эти участки кода, и мы потенциально можем потерять начало или конец замера времени, поэтому можно воспользоваться концепцией RAII, и сделать это следующим красивым образом, передав в функцию замера времени лямбду кода, время которого будет замеряться:
Притом мы не теряем локальные переменные благодаря использованию лямбд.
Предположим, что у нас есть функция вычисления интеграла одномерной функции:
Тогда двойной интеграл двумерной функции можно вычислить следующим образом:
Некоторые люди делают это с помощью копипаста, но думаю не стоит вам объяснять чем плох копипаст по сравнению с этим подходом.
В МО многомерные методы нахождения минимума функции используют одномерную функцию оптимизации. И было задание задавать различные методы одномерной оптимизации, чтобы протестировать их эффективность конкретно в этой среде. Ну раз так, то мы не будем в программе жестко задавать функцию одномерной оптимизации, а будем передавать её как аргумент функции многомерной оптимизации:
На самом деле я не использовал это на практике, а пример этого паттерна взял из библиотеки дифференциальной эволюции.
Там же можно увидеть паттерн termination strategy, при помощи которого можно передавать в метод функцию, которая будет решать когда завершать метод. Но в рамках нашей учебной программы это излишная абстракция, и различные стратегии завершения метода никогда не применятся.
Инкапсулируем с помощью std::function
В УМФ у нас есть задача получить конечно-элементную аппроксимацию функции на основе правой части дифференциального уравнения и сетки конечных элементов. Учитывая эту информацию, решатель МКЭ может выглядеть следующим образом:
Поэтому мы должны каким-то образом внутри функции решения дифференциального уравнения выставлять краевые условия.
Но это ужасный стиль! Зачем нам находить значение функции при помощи численных методов, которую мы уже знаем? Это выглядит как бред, поэтому более красивым может быть передавать функцию, которая выставляет краевые условия:
Это очень красивое решение, которое может быть применено при решении реальных задач, если мы реально не знаем истинную функцию, но знаем значения краевых условий. Это значительно повышает абстрактность кода, позволяет его использовать в других проектах.
Так как вы можете использовать это на практике, более подробно смотрите код из курсовой по УМФ:
About
Статья о применении std::function в универе для лаб
C++ :: Чувствуя себя в гостях
Особенности нового стандарта языка
Автор: Владимиров Константин Игоревич
Опубликовано: 28.05.2012
Исправлено: 10.12.2016
Версия текста: 1.1
1. Введение
Цифры в круглых скобках в тексте статьи означают нормативные ссылки на стандарт обсуждаемого языка (автор старался, чтобы то, какой стандарт обсуждается, было понятно из контекста). Цитируются только стандарты ISO/IEC, поэтому цитируется C90, а не C89. Стандарт ISO/IEC 9899:2011 не обсуждается и не цитируется, поскольку мало отличается от С99 и слабо поддержан современными компиляторами. Заголовочные файлы, в которых размещены упомянутые в тексте библиотечные функции, следуют из пунктов стандарта, поэтому не упоминаются в тексте и не включаются в примеры кода.
2. Rvalue references и std::move
Ссылки (references) – это то, что наиболее сложно воспринимается при переходе с C на C++. Человеку с опытом программирования на C может быть непонятно, зачем они нужны, когда уже есть такие удобные и привычные указатели, которые позволяют делать всё то же самое, только лучше. Подлинное осознание того, каким именно инструментом являются ссылки, и как ими оперируют, приходит с опытом программирования на C++. При переходе на новый стандарт C++11 идея о том, что теперь есть ещё один вид ссылок, ссылки на rvalue (rvalue references), кажется новаторской и неочевидной (особенно неочевидна её полезность). Скорее всего, многим придётся преодолеть внутреннее сопротивление, прежде чем rvalue references станут удобным и привычным инструментом. Но прежде чем переходить к их описанию, есть смысл напомнить о том, что такое rvalue и lvalue.
2.1. Отличаем rvalue от lvalue
Прежде чем появиться в C++, термин lvalue был документально зафиксирован в стандарте языка C, поэтому логично начать изложение с него. Для языка C lvalue (от left hand side value) – то, что может появиться слева в выражении присваивания. Формально (6.3.2.1 стандарта C99), «An lvalue is an expression with an object type or an incomplete type other than void» (под object type в стандарте понимаются все типы, не являющиеся function type или incomplete type).
Собственно, в стандарте языка C90 и последовавшем за ним C99 нет термина rvalue. В C99 есть оговорка «What is sometimes called ‘‘rvalue’’ is in this International Standard described as the ‘‘value of an expression’’», позволяющая предположить, что к этому времени этот термин существовал и использовался, но фиксировать его строго в случае языка C было не нужно.
Всё изменилось с приходом языка C++, который принёс с собой references. Выражение из прошлого примера:
может быть ошибкой, если foo возвращает значение, но совершенно корректно, если foo возвращает ссылку. Чтобы разрешить эту неоднозначность, стандарт C++ 98 объявляет (3.10.1) два термина – lvalue и rvalue, причём каждое выражение языка C++98 является либо тем, либо другим. При этом определение в (3.10.2) гласило «An lvalue refers to an object or function.»
То есть lvalue – это не первоклассный объект, как в C, а просто некое выражение, ссылающееся на область памяти. Таким образом, в качестве главного различия выступает возможность взять адрес, а не отношение к операции равенства.
В некотором смысле, lvalue в C++98 содержательно становится locator value (а не left hand side value). Так, например, если есть указатель val, то выражение val+1 можно разыменовать, но нельзя взять его адрес.
Cтандарт С++11 объявляет (3.10) пять терминов: lvalue, rvalue, xvalue, а также glvalue (обобщение lvalue и xvalue) и prvalue (более узкая специализация rvalue). Базовыми терминами являются знакомые нам lvalue, rvalue и новый термин xvalue (от expiring value). То, что понималось под rvalue в C++98, в C++11 стало prvalue (в то время как rvalue обобщает xvalue и prvalue), а новый термин glvalue обобщает lvalue и xvalue. Можно (неточно, но образно) сказать, что prvalue – это то, брать адрес от чего нельзя, lvalue – от чего можно, а xvalue – от чего бесполезно.
Если всё это не вызывает вопросов, настало время перейти к объяснению ссылок.
2.2. Отличаем rvalue refernces от lvalue references
В C++11, если X – это тип, то X& – это lvalue reference, а X&& – это rvalue reference для этого типа (8.3.2.2). При этом важно понимать, что X&, X&& – это разные типы. С одной стороны, семантически они похожи, и о них можно говорить как о «ссылках вообще». Тем не менее, поскольку это разные типы, для них работает перегрузка:
При этом сама по себе rvalue reference может быть lvalue (и является им, если она именована).
Это очень важное правило. Представьте ситуацию, в которой объявлен класс Base, и в нём есть необходимость определить конструкторы:
Теперь в наследуемом от него классе Derived есть необходимость эти конструкторы переопределить:
Перемещающий конструктор переопределён неверно. Поскольку у rhs есть имя, оно есть lvalue, а значит, Base(rhs) в данном случае вызовет снова Base(Base const & rhs), что, вероятно, нежелательно. Вместо этого следует писать
Сюрпризы подобные этому ожидают, казалось бы, в очевидных вещах. Для примера можно взять общее утверждение, что ссылка на ссылку – невозможна. Это все знают и тысячу раз отвечали на собеседованиях. Что же, этот ответ всё ещё верен, и стандарт C++11 (8.3.2.5) запрещает ссылки на ссылки, указатели на ссылки и массивы ссылок. Но как быть, когда в шаблоне или при typedef получается смесь правых и левых ссылочных типов? Это способно поставить в тупик. Поэтому в этом месте следует разобраться с тем, как работают ссылки в C++11.
2.3. Свёртка ссылочных типов
Стандарт (8.3.2.6) определяет следующие правила свёртки ссылок, применимые для определений typedef и decltype, а также параметров шаблонов:
| СОВЕТ Пусть определён шаблон Пусть теперь функция foo вызвана с аргументом x типа X, причём x является lvalue. Тогда T разрешается в X&, а реальным типом аргумента t будет X& &&, то есть (см. выше правила свёртки) X&. Если же x является rvalue, то T разрешается в X и реальным типом аргумента t будет X&&. Стандарт также определяет функтор remove_reference (20.9.7.2), который позволяет получить не-ссылочный тип из ссылочного. Он работает похоже на static_cast и прочие привычные вещи. Аналогично (но наоборот) работает пара add_lvalue_reference/add_rvalue_reference. (Я надеюсь, никто никогда не напишет такую строчку в реальном коде). Теперь настала пора посмотреть, зачем же комитетом по стандартизации были введены все эти сложности. 2.4. Применение rvalue-ссылокВ стандарте С++98 условная реализация, соответствующая требованиям стандарта к функции std::swap (25.2.2), требовала использования копирования: В условной реализации С++11 удовлетворяющая стандарту реализация библиотечной функции std::swap (20.2.2) может выглядеть, например, так: В этой реализации нет операций копирования (без которых не обойтись в C++98). Функция std::move позволяет здесь реализовать семантику перемещения, поскольку она работает так: из любого аргумента std::move делает rvalue. Эта функция, в свою очередь, в удовлетворяющем стандарту (20.2.3) виде может быть определена так: Методологически полезно проследить, что будет, если вызвать std::move с аргументом, имеющим тип X и являющимся lvalue. 1) T будет разрешено в X& 2) тип аргумента X& && a будет свёрнут в X& a 3) remove_reference ::type&& будет означать X&&, который и будет значением RvalRef Итак, получим эквивалент: Сама идея, что теперь обмен значениями может быть семантически выражен как обмен значениями, а не копирование (а значит, и оптимизирован соответствующим образом), после её осознания, приводит людей в лёгкую эйфорию. В конце концов, стремление к максимальной эффективности – нормальное стремление. Тем не менее, с функцией std::move следует быть осторожным.
|