Применения std::function
Рассказываю как улучшить качество кода при помощи типа данных функций и лямбда-функций.
30 января 2019
| 12 мин
#Статья об использовании std::function
Поводом для написания данной статьи стало желание систематизировать всё то, чему я научился в процессе кодинга лаб по таким предметам, как Численные методы; Уравнения математической физики; Методы оптимизации. Надеюсь эта статья будет полезна не только людям, которые учатся в НГТУ на факультете ФПМИ, но и широкому кругу читателей.
#Введение
В Си/C++ имеется возможность передавать функцию в качестве аргумента функции как указатель на функцию. Вы наверняка знаете этот страшный синтаксис:
Начиная с C++11 появился удобный интерфейс для описания функциональных объектов под названием std::function , он имеет более красивый синтаксис:
Причём, в отличие от Си, в C++ таким образом можно описывать любой функциональный объект, то есть объект, допускающий вызов операции () :
Аналогичным образом можно использовать лямбды:
Таким образом, можно передавать функции в качестве аргументов функций.
#Численное вычисление производной
Пусть у нас имеется некоторая функция f , которая получает double и возвращает double . Тогда можно численно вычислить производную этой функции следующим образом:
Но писать для каждой функции другую функцию, вычисляющую её производную непрактично! Поэтому такую проблему можно решить с помощью std::function :
Данная функция получает функцию, и возвращает функцию, которая считает её производную. Пример использования:
А далее представлен код для вычисления первой и второй производных функций, взятый из курсовой по УМФ, смело используйте его в своих проектах.
Может показаться, что если уменьшить величину 0.001 , то точность повысится, но это не так, на практике у меня точность только падала, а при этом значении уже очень хорошо расчитывается производная.
#Пример использования std::bind
Окей, у нас существует функция для расчета производной одномерной функции, но что если у нас функция принимает два аргумента, например f(x, y) . Как посчитать её производную по одному параметру?
На помощь приходит std::bind — функция, которая получает функцию, список аргументов и возвращает другую функцию. И лучше увидеть пример, чтобы понять как он работает:
Где std::placeholders::_1 показывает первый параметр внешней функции, которую возвратит std::bind .
К сожалению в этом случае функция fx получается функцией одной переменной, при фиксированном y=0 . Чтобы получить функцию fx , которая принимает два параметра, но при этом возвращает производную по x , можно воспользоваться следующим трюком:
Наверное можно было бы его улучшить, чтобы не копипастить эти строки, и придумать новый std::bind2 , при помощи шаблонной магии C++, но я такое не умею. Если вы сможете это сделать, обязательно pull-request’ните сюда!
Или другой пример:
#Автоматический расчет правой части
В таком предмете, как УМФ требуется решить следующее дифференциальное уравнение:
-div(lambda * grad u) + gamma * u + sigma * du/dt = f ,
где u = u(x, y, t) является неизвестной функцией. Для численного решения используется Метод Конечных Элементов.
Для декартовых координат это уравнение раскладывается в lambda*(d^2 u/dx^2 + d^2 u/dy^2) + gamma * u + sigma * du/dt = f
Для тестирования программы-решателя мы придумываем некоторую функцию u , например: u = x*x + y*y + t , придумываем значения констант lambda , gamma , sigma , и согласно верхнему уравнению можно вычислить функцию f , при которой дифференциальное уравнение превращается в тождество.
Для того, чтобы тестировать нашу программу на широком спектре функций, можно написать функцию, которая будет автоматически рассчитывать эту правую часть на основе вышеописанных функций для вычисления производных:
Код взят опять же из репозитория по УМФ.
Таким образом, мы получаем функцию f на основе известной нам функции u .
Это избавляет от лишней рутины ручного вычисления производных для функций при тестировании, автоматизируя этот процесс, также снижается вероятность ошибиться.
#Обертка для подсчета вызовов функции
Например, нам надо протестировать какой-то метод, находящий минимум заданной функции f . Надо узнать насколько он хороший путем определения сколько раз ему потребовалось обратиться к функции f , потому что обычно самой дорогой операцией считается именно вызов функции f .
Это можно сделать не вмешиваясь в код этого метода, явным образом выставляя count++ при каждом вызове функции, послав вместо функции обёртку над ней:
#Замер времени
У нас стоит задача замерить время работы какого-то кода. Можно поступить следующим образом:
Но это решение плохо тем, что нам постоянно надо копировать эти участки кода, и мы потенциально можем потерять начало или конец замера времени, поэтому можно воспользоваться концепцией RAII, и сделать это следующим красивым образом, передав в функцию замера времени лямбду кода, время которого будет замеряться:
Притом мы не теряем локальные переменные благодаря использованию лямбд.
#Двойной интеграл
Предположим, что у нас есть функция вычисления интеграла одномерной функции:
Тогда двойной интеграл двумерной функции можно вычислить следующим образом:
#Функции тестирования
Представим, у нас есть множество методов, решающих одну задачу, но каждый по-разному, при этом интерфейс у них одинаковый. И стоит задача протестировать их все единым образом, построить таблицы. Это тоже можно красиво сделать с помощью std::function , написать прототип функции метода:
Некоторые люди делают это с помощью копипаста, но думаю не стоит вам объяснять чем плох копипаст по сравнению с этим подходом.
#Одномерный поиск
В МО многомерные методы нахождения минимума функции используют одномерную функцию оптимизации. И было задание задавать различные методы одномерной оптимизации, чтобы протестировать их эффективность конкретно в этой среде. Ну раз так, то мы не будем в программе жестко задавать функцию одномерной оптимизации, а будем передавать её как аргумент функции многомерной оптимизации:
#Паттерн listener
Иногда бывает так, что вам нужно выводить информацию о внутреннем состоянии метода, например, вы решаете задачу оптимизации, и вам надо на каждой итерации выводить текущее решение, значение градиента функции в этой точке итд, чтобы строить таблицу. Вместо того, чтобы делать это явно, можно передавать в функцию метода функцию listener , которая будет получать всё внутреннее состояние метода, и уже у себя внутри решать что с этим делать.
В одном случае она может выводить это на экран, в другом случае — в файл, а в третьем, когда нужная максимальная производительность — ничего не делать с этой информацией.
На самом деле я не использовал это на практике, а пример этого паттерна взял из библиотеки дифференциальной эволюции.
Там же можно увидеть паттерн termination strategy, при помощи которого можно передавать в метод функцию, которая будет решать когда завершать метод. Но в рамках нашей учебной программы это излишная абстракция, и различные стратегии завершения метода никогда не применятся.
#Инкапсулируем с помощью std::function
В УМФ у нас есть задача получить конечно-элементную аппроксимацию функции на основе правой части дифференциального уравнения и сетки конечных элементов. Учитывая эту информацию, решатель МКЭ может выглядеть следующим образом:
Возвращается вектор, потому что мы находим только аппроксимацию весов q , именно они и возвращаются в методе.
Но при решении МКЭ мы не можем игнорировать ту вещь, что нам нужно выставлять краевые условия. Краевые условия — это известные нам значения функции на краях области. Не имея краевых значений, невозможно решить задачу, она просто не сойдется.
Поэтому мы должны каким-то образом внутри функции решения дифференциального уравнения выставлять краевые условия.
В нашем курсе УМФ мы не решаем реальные задачи, а лишь исследуем насколько хорошо метод справляется с известными нам функциями u , поэтому может появиться соблазн написать следующим образом:
Но это ужасный стиль! Зачем нам находить значение функции при помощи численных методов, которую мы уже знаем? Это выглядит как бред, поэтому более красивым может быть передавать функцию, которая выставляет краевые условия:
Это очень красивое решение, которое может быть применено при решении реальных задач, если мы реально не знаем истинную функцию, но знаем значения краевых условий. Это значительно повышает абстрактность кода, позволяет его использовать в других проектах.
Так как вы можете использовать это на практике, более подробно смотрите код из курсовой по УМФ:
#Заключение
Было показано каким образом std::function позволит вам избавиться от лишнего копипаста и инкапсулировать код и данные и в принципе сделать код намного более абстрактным и красивым. Если вы придумали ещё одно интересное применение std::function , то пишите в комментариях!
std::function
Шаблон класса std::function — это универсальная оболочка полиморфной функции. Экземпляры std::function могут хранить, копировать и вызывать любой объект CopyConstructible Callable. target — функции, лямбда-выражения , выражения связывания или другие функциональные объекты, а также указатели на функции-члены и указатели на элементы данных.
Хранимый вызываемый объект называется target из std::function . Если std::function содержит цели, она вызывается empty , Вызывая target of an empty std::function приводит к std::bad_function_call исключения std :: bad_function_call .
std::function удовлетворяет требованиям CopyConstructible и CopyAssignable .
Member types
| Type | Definition |
|---|---|
| result_type | R |
| argument_type (устарел в C ++ 17) (удален в C ++ 20) | T если sizeof. (Args)==1 и T является первым и единственным типом в Args. |
| first_argument_type (устарело в C ++ 17) (удалено в C ++ 20) | T1 , если sizeof. (Args)==2 и T1 — первый из двух типов в Args. |
| second_argument_type (устарело в C ++ 17) (удалено в C ++ 20) | T2 , если sizeof. (Args)==2 и T2 — второй из двух типов в Args. |
Member functions
Target access
Non-member functions
Helper classes
Руководства по удержанию (начиная с C ++ 17)
Notes
Следует соблюдать осторожность, когда std::function , тип результата которой является ссылкой, инициализируется из лямбда-выражения без возвращаемого типа. Из-за того, как работает автовывод, такое лямбда-выражение всегда будет возвращать значение prvalue. Следовательно, результирующая ссылка обычно будет привязана к временному объекту, время жизни которого заканчивается, когда возвращается std::function::operator() .
Если std::function , возвращающая ссылку, инициализируется из функции или объекта функции, возвращающего значение prvalue (включая лямбда-выражение без возвращаемого типа), программа имеет неправильный формат, поскольку привязка возвращаемой ссылки к временному объекту запрещенный.
What is the purpose of std::function and how to use it?
It is necessary for me to use std::function but I don’t know what the following syntax means.
What is the goal of using std::function ? Is it to make a pointer to a function?
3 Answers 3
std::function is a type erasure object. That means it erases the details of how some operations happen, and provides a uniform run time interface to them. For std::function , the primary 1 operations are copy/move, destruction, and ‘invocation’ with operator() — the ‘function like call operator’.
In less abstruse English, it means that std::function can contain almost any object that acts like a function pointer in how you call it.
The signature it supports goes inside the angle brackets: std::function<void()> takes zero arguments and returns nothing. std::function< double( int, int ) > takes two int arguments and returns double . In general, std::function supports storing any function-like object whose arguments can be converted-from its argument list, and whose return value can be converted-to its return value.
It is important to know that std::function and lambdas are different, if compatible, beasts.
The next part of the line is a lambda. This is new syntax in C++11 to add the ability to write simple function-like objects — objects that can be invoked with () . Such objects can be type erased and stored in a std::function at the cost of some run time overhead.
[]() < code >in particular is a really simple lambda. It corresponds to this:
an instance of the above simple pseudo-function type. An actual class like the above is "invented" by the compiler, with an implementation defined unique name (often including symbols that no user-defined type can contain) (I do not know if it is possible that you can follow the standard without inventing such a class, but every compiler I know of actually creates the class).
The full lambda syntax looks like:
But many parts can be omitted or left empty. The capture_list corresponds to both the constructor of the resulting anonymous type and its member variables, the argument_list the arguments of the operator() , and the return type the return type. The constructor of the lambda instance is also magically called when the instance is created with the capture_list.
Note that in c++20 template arguments were added to lambdas, and that isn’t covered above.
1 In addition, RTTI is stored (typeid), and the cast-back-to-original-type operation is included.
Name already in use
If nothing happens, download GitHub Desktop and try again.
Launching GitHub Desktop
If nothing happens, download GitHub Desktop and try again.
Launching Xcode
If nothing happens, download Xcode and try again.
Launching Visual Studio Code
Your codespace will open once ready.
There was a problem preparing your codespace, please try again.
Latest commit
Git stats
Files
Failed to load latest commit information.
README.md
Статья об использовании std::function
Поводом для написания данной статьи стало желание систематизировать всё то, чему я научился в процессе кодинга лаб по таким предметам, как Численные методы; Уравнения математической физики; Методы оптимизации. Надеюсь эта статья будет полезна не только людям, которые учатся в НГТУ на факультете ФПМИ, но и широкому кругу читателей.
В Си/C++ имеется возможность передавать функцию в качестве аргумента функции как указатель на функцию. Вы наверняка знаете этот страшный синтаксис:
Начиная с C++11 появился удобный интерфейс для описания функциональных объектов под названием std::function , он имеет более красивый синтаксис:
Причём, в отличие от Си, в C++ таким образом можно описывать любой функциональный объект, то есть объект, допускающий вызов операции () :
Аналогичным образом можно использовать лямбды:
Таким образом, можно передавать функции в качестве аргументов функций.
Численное вычисление производной
Пусть у нас имеется некоторая функция f , которая получает double и возвращает double . Тогда можно численно вычислить производную этой функции следующим образом:
Но писать для каждой функции другую функцию, вычисляющую её производную непрактично! Поэтому такую проблему можно решить с помощью std::function :
Данная функция получает функцию, и возвращает функцию, которая считает её производную. Пример использования:
А далее представлен код для вычисления первой и второй производных функций, взятый из курсовой по УМФ, смело используйте его в своих проектах.
Может показаться, что если уменьшить величину 0.001 , то точность повысится, но это не так, на практике у меня точность только падала, а при этом значении уже очень хорошо расчитывается производная.
Пример использования std::bind
Окей, у нас существует функция для расчета производной одномерной функции, но что если у нас функция принимает два аргумента, например f(x, y) . Как посчитать её производную по одному параметру?
На помощь приходит std::bind — функция, которая получает функцию, список аргументов и возвращает другую функцию. И лучше увидеть пример, чтобы понять как он работает:
Где std::placeholders::_1 показывает первый параметр внешней функции, которую возвратит std::bind .
К сожалению в этом случае функция fx получается функцией одной переменной, при фиксированном y=0 . Чтобы получить функцию fx , которая принимает два параметра, но при этом возвращает производную по x , можно воспользоваться следующим трюком:
Наверное можно было бы его улучшить, чтобы не копипастить эти строки, и придумать новый std::bind2 , при помощи шаблонной магии C++, но я такое не умею. Если вы сможете это сделать, обязательно pull-request’ните сюда!
Или другой пример:
Автоматический расчет правой части
В таком предмете, как УМФ требуется решить следующее дифференциальное уравнение:
-div(lambda * grad u) + gamma * u + sigma * du/dt = f ,
где u = u(x, y, t) является неизвестной функцией. Для численного решения используется Метод Конечных Элементов.
Для декартовых координат это уравнение раскладывается в lambda*(d^2 u/dx^2 + d^2 u/dy^2) + gamma * u + sigma * du/dt = f
Для тестирования программы-решателя мы придумываем некоторую функцию u , например: u = x*x + y*y + t , придумываем значения констант lambda , gamma , sigma , и согласно верхнему уравнению можно вычислить функцию f , при которой дифференциальное уравнение превращается в тождество.
Для того, чтобы тестировать нашу программу на широком спектре функций, можно написать функцию, которая будет автоматически рассчитывать эту правую часть на основе вышеописанных функций для вычисления производных:
Код взят опять же из репозитория по УМФ.
Таким образом, мы получаем функцию f на основе известной нам функции u .
Это избавляет от лишней рутины ручного вычисления производных для функций при тестировании, автоматизируя этот процесс, также снижается вероятность ошибиться.
Обертка для подсчета вызовов функции
Например, нам надо протестировать какой-то метод, находящий минимум заданной функции f . Надо узнать насколько он хороший путем определения сколько раз ему потребовалось обратиться к функции f , потому что обычно самой дорогой операцией считается именно вызов функции f .
Это можно сделать не вмешиваясь в код этого метода, явным образом выставляя count++ при каждом вызове функции, послав вместо функции обёртку над ней:
У нас стоит задача замерить время работы какого-то кода. Можно поступить следующим образом:
Но это решение плохо тем, что нам постоянно надо копировать эти участки кода, и мы потенциально можем потерять начало или конец замера времени, поэтому можно воспользоваться концепцией RAII, и сделать это следующим красивым образом, передав в функцию замера времени лямбду кода, время которого будет замеряться:
Притом мы не теряем локальные переменные благодаря использованию лямбд.
Предположим, что у нас есть функция вычисления интеграла одномерной функции:
Тогда двойной интеграл двумерной функции можно вычислить следующим образом:
Представим, у нас есть множество методов, решающих одну задачу, но каждый по-разному, при этом интерфейс у них одинаковый. И стоит задача протестировать их все единым образом, построить таблицы. Это тоже можно красиво сделать с помощью std::function , написать прототип функции метода:
Некоторые люди делают это с помощью копипаста, но думаю не стоит вам объяснять чем плох копипаст по сравнению с этим подходом.
В МО многомерные методы нахождения минимума функции используют одномерную функцию оптимизации. И было задание задавать различные методы одномерной оптимизации, чтобы протестировать их эффективность конкретно в этой среде. Ну раз так, то мы не будем в программе жестко задавать функцию одномерной оптимизации, а будем передавать её как аргумент функции многомерной оптимизации:
Иногда бывает так, что вам нужно выводить информацию о внутреннем состоянии метода, например, вы решаете задачу оптимизации, и вам надо на каждой итерации выводить текущее решение, значение градиента функции в этой точке итд, чтобы строить таблицу. Вместо того, чтобы делать это явно, можно передавать в функцию метода функцию listener , которая будет получать всё внутреннее состояние метода, и уже у себя внутри решать что с этим делать.
В одном случае она может выводить это на экран, в другом случае — в файл, а в третьем, когда нужная максимальная производительность — ничего не делать с этой информацией.
На самом деле я не использовал это на практике, а пример этого паттерна взял из библиотеки дифференциальной эволюции.
Там же можно увидеть паттерн termination strategy, при помощи которого можно передавать в метод функцию, которая будет решать когда завершать метод. Но в рамках нашей учебной программы это излишная абстракция, и различные стратегии завершения метода никогда не применятся.
Инкапсулируем с помощью std::function
В УМФ у нас есть задача получить конечно-элементную аппроксимацию функции на основе правой части дифференциального уравнения и сетки конечных элементов. Учитывая эту информацию, решатель МКЭ может выглядеть следующим образом:
Возвращается вектор, потому что мы находим только аппроксимацию весов q , именно они и возвращаются в методе.
Но при решении МКЭ мы не можем игнорировать ту вещь, что нам нужно выставлять краевые условия. Краевые условия — это известные нам значения функции на краях области. Не имея краевых значений, невозможно решить задачу, она просто не сойдется.
Поэтому мы должны каким-то образом внутри функции решения дифференциального уравнения выставлять краевые условия.
В нашем курсе УМФ мы не решаем реальные задачи, а лишь исследуем насколько хорошо метод справляется с известными нам функциями u , поэтому может появиться соблазн написать следующим образом:
Но это ужасный стиль! Зачем нам находить значение функции при помощи численных методов, которую мы уже знаем? Это выглядит как бред, поэтому более красивым может быть передавать функцию, которая выставляет краевые условия:
Это очень красивое решение, которое может быть применено при решении реальных задач, если мы реально не знаем истинную функцию, но знаем значения краевых условий. Это значительно повышает абстрактность кода, позволяет его использовать в других проектах.
Так как вы можете использовать это на практике, более подробно смотрите код из курсовой по УМФ:
Было показано каким образом std::function позволит вам избавиться от лишнего копипаста и инкапсулировать код и данные и в принципе сделать код намного более абстрактным и красивым. Если он был вам полезен, обязательно ставьте звёзды! А если вы придумали ещё одно интересное применение std::function , буду рад принять pull request!