Передача аргументов в указатель на функцию
Есть следующий код, который позволяет решать уравнение методом касательных:
#include <iostream>
double q_func(double x, double k, double y)
{
return x * pow(1 - (k-1)/(k+1) * x * x, 1/(k-1)) * pow((k+1)/2, 1/(k-1)) - y;
}
double solve(double (*f)(double, double, double), double k, double y, double x_0=2.3, int n=50)
{
double x = x_0;
double dx = 1e-5;
double df = (f(x+dx, k, y)-f(x, k, y))/dx;
for (int i=0; i<=n;i++){
x_0 = x;
x -= f(x, k, y) / df;
if (abs(x - x_0) < 1e-6)
return x;
}
return x;
}
int main() {
double x = solve(q_func, 1.4, 0.25);
std::cout << x << std::endl;
return 0;
}
Функция q_func принимает 3 аргумента и сейчас, чтобы решить уравнение (найти x) я передаю в функцию solve дополнительно два аргумента k и y.
Получается, что при добавлении параметра в функцию q_func мне необходимо дополнительно переписывать и функцию solve. При этом уравнение с двумя параметрами уже решить нельзя будет, нужно будет сохранить старую сигнатуру, получается дублирование кода. Можно ли как-то этого избежать?
Первично в голову приходить передавать vector или map вторым аргументом, но может есть более С++-path решение?
Ответы (2 шт):
f - это функция от x. k не её параметр, а деталь реализации. rhs тоже не аргумент, а значение y в уравнении y = f(x). Так что надо делать f функцией одного параметра, а всё остальное из сигнатуры исключить.
В современном C++ для передачи в функцию настроек лучше всего подходит лямбда. Функцию solve придётся сделать шаблонной. k передаётся в f через замыкание:
#include <cmath>
#include <iostream>
template <typename Func>
double solve(Func f, double y, double x_0 = 2.3, int n = 50) {
double x = x_0;
double dx = 1e-5;
for (int i = 0; i < n; ++i) {
x_0 = x;
double df = (f(x + dx) - f(x)) / dx;
x -= (f(x) - y) / df;
if (x - x_0 < 1e-6) {
return x;
}
}
return x;
}
int main() {
double k = 1.4;
auto q_func = [k](double x) {
return
x *
pow(1 - (k - 1) / (k + 1) * x * x, 1 / (k - 1)) *
pow((k + 1) / 2, 1 / (k - 1))
;
};
std::cout << solve(q_func, 0.25) << std::endl;
}
Если вам не нравится лямбда и шаблонный код, есть более традиционный способ: завести интерфейс с double operator()(double x) и работать с его наследниками. Получится что-то такое:
#include <cmath>
#include <iostream>
class Func {
public:
virtual ~Func() {}
virtual double operator()(double x) const = 0;
};
double solve(const Func &f, double y, double x_0 = 2.3, int n = 50) {
double x = x_0;
double dx = 1e-5;
for (int i = 0; i < n; ++i) {
x_0 = x;
double df = (f(x + dx) - f(x)) / dx;
x -= (f(x) - y) / df;
if (x - x_0 < 1e-6) {
return x;
}
}
return x;
}
class QFunc : public Func {
public:
QFunc(double k) : k(k) {}
virtual double operator()(double x) const override {
return
x *
pow(1 - (k - 1) / (k + 1) * x * x, 1 / (k - 1)) *
pow((k + 1) / 2, 1 / (k - 1))
;
}
private:
double k;
};
int main() {
QFunc q_func(1.4);
std::cout << solve(q_func, 0.25) << std::endl;
}
P.S. Что касается математики, df лучше пересчитывать внутри цикла. Иначе не работают признаки гарантирующие сходимость итераций к корню и скорость этой сходимости.
Есть несколько подходов как решить эту проблему. Можно передать в функцию solve всегда только функцию принимающую только один параметр x. А если надо передать функцию с большим количеством параметров, то просто создать функцию обёртку с 1 параметром x.
#include <math.h>
#include <iostream>
double q_func(double x, double k, double rhs) {
return x * pow(1 - (k - 1) / (k + 1) * x * x, 1 / (k - 1)) *
pow((k + 1) / 2, 1 / (k - 1)) -
rhs;
}
double solve(double (*f)(double), double x_0 = 2.3, int n = 50) {
double x = x_0;
double dx = 1e-5;
double df = (f(x + dx) - f(x)) / dx;
for (int i = 0; i <= n; i++) {
x_0 = x;
x = x - f(x) / df;
if (x - x_0 < 1e-6) {
return x;
}
}
return x;
}
int main() {
auto wrapper = [](double x) { return q_func(x, 1.4, 0.25); };
double x = solve(wrapper);
std::cout << x << std::endl;
return 0;
}
Можно сделать так чтобы solve принимала любые
Это сработает если k, rhs и остальные параметры известны на этапе компиляции. Если нет то не получится сконвертировать лямбду в указатель на функцию. Можно добавить немного концептов чтобы принимать что угодно вызываемое. Это позволяет передавать лямбды с неизвестными на этапе компиляции параметрами.
#include <math.h>
#include <concepts>
#include <iostream>
double q_func(double x, double k, double rhs) {
return x * pow(1 - (k - 1) / (k + 1) * x * x, 1 / (k - 1)) *
pow((k + 1) / 2, 1 / (k - 1)) -
rhs;
}
template <typename Func>
requires std::invocable<Func, double> &&
std::same_as<double, std::invoke_result_t<Func, double>>
double solve(Func f, double x_0 = 2.3, int n = 50) {
double x = x_0;
double dx = 1e-5;
double df = (f(x + dx) - f(x)) / dx;
for (int i = 0; i <= n; i++) {
x_0 = x;
x = x - f(x) / df;
if (x - x_0 < 1e-6) {
return x;
}
}
return x;
}
int main() {
double k = 1.4;
double rhs = 0.25;
auto wrapper = [=](double x) { return q_func(x, k, rhs); };
double x = solve(wrapper);
std::cout << x << std::endl;
auto wrapper2 =
[](double first, double second) {
return [=](double x) { return q_func(x, first, second); };
};
double x2 = solve(wrapper2(k, rhs));
std::cout << x2 << std::endl;
return 0;
}
Также можно использовать темплейты чтобы передавать любое число параметров в solve и из solve в переданную функцию. Если использовать этот подход параметры для функции должны идти в самом конце что не позволит n и x_0 быть параметрами по умолчанию.
#include <math.h>
#include <iostream>
double q_func(double x, double k, double rhs) {
return x * pow(1 - (k - 1) / (k + 1) * x * x, 1 / (k - 1)) *
pow((k + 1) / 2, 1 / (k - 1)) -
rhs;
}
template <typename... Args>
double solve(double x_0, int n, double (*f)(double, Args...),
Args... args) {
double x = x_0;
double dx = 1e-5;
double df = (f(x + dx, args...) - f(x, args...)) / dx;
for (int i = 0; i <= n; i++) {
x_0 = x;
x = x - f(x, args...) / df;
if (x - x_0 < 1e-6) {
return x;
}
}
return x;
}
int main() {
double k = 1.4;
double rhs = 0.25;
double x = solve(2.3, 50, q_func, k, rhs);
std:: cout << x << std::endl;
return 0;
}