Я пытался решить следующую проблему на С++. Я хотел бы определить структуру, содержащую параметры конфигурации для некоторого программного модуля. Параметры конфигурации в основном представляют собой значения с плавающей запятой и бывают двух типов:
Вот пример
struct Configuration {
float param_independent_01;
float param_independent_02;
float param_dependent_01; // param_independent_01 + param_independent_02
float param_dependent_02; // 1.5f*param_independent_01/(param_independent_01 + param_independent_02)
};
Я искал решение, которое позволяет клиентскому коду устанавливать значения только для независимых параметров, а значения зависимых параметров будут вычисляться автоматически за сценой.
Configuration config = {
param_independent_01 = 0.236f,
param_independent_02 = 0.728f
// param_dependent_01 = 0.236f + 0.728f
// param_dependent_02 = 1.5f*0.236f/(0.236f + 0.728f)
};
Я предполагаю, что структура конфигурации будет создана только один раз, а значения параметров известны во время компиляции. Может ли кто-нибудь дать мне совет, как это сделать на С++?
Должны быть методы, а не могут быть методы.
@MichaëlRoy согласен, отредактировано.
@wohlstad большое спасибо за вашу реакцию. Я вижу в основном два недостатка в вашей идее. Во-первых, независимые и зависимые параметры будут использоваться по-разному (доступ к переменным и вызов метода). Второй заключается в том, что зависимые параметры будут пересчитываться снова и снова, несмотря на то, что их значения не меняются (они известны во время компиляции).
@ Стив, это зависит от твоей системы. Простые и относительно нечастые вычисления могут быть лучше, чем хранение избыточных данных (и риск ошибок из-за несогласованности). Но если это критично в вашем случае - опубликованный ответ предлагает способ сделать это.
Одним из подходов к достижению такого поведения является использование списка инициализации конструктора C++.
struct Configuration {
float param_independent_01;
float param_independent_02;
float param_dependent_01;
float param_dependent_02;
Configuration(float p1, float p2) :
param_independent_01(p1),
param_independent_02(p2),
param_dependent_01(p1 + p2),
param_dependent_02(1.5f * p1 / (p1 + p2)
)
{}
};
int main() {
Configuration config(0.236f, 0.728f);
return 0;
}
Благодарю за ваш ответ. У меня довольно много параметров (нижние десятки). В таком случае это будет означать огромный конструктор, который кажется мне очень подверженным ошибкам.
Если вы знаете, что конфигурация не изменится во время выполнения, вы можете реализовать конструктор constexpr
для Configuration
, а затем определить переменную constexpr Configuration
. Построение будет выполнено во время компиляции (см. сгенерированный ассемблерный код для ссылки на Godbolt ниже).
Если вы хотите убедиться, что конфигурация не изменится во время выполнения, я бы превратил Configuration
в класс с закрытыми членами и просто предоставил средства доступа для этих членов.
Заметьте также, что конструктор может бросить (из-за деления на ноль). Если вы хотите взять ситуацию под контроль, вы можете try-catch
установить зависимый параметр 2 в теле конструктора.
#include <fmt/format.h>
#include <iostream>
class Configuration {
float param_independent_01;
float param_independent_02;
float param_dependent_01;
float param_dependent_02;
public:
constexpr Configuration(float p1, float p2)
: param_independent_01{p1}
, param_independent_02{p2}
, param_dependent_01{p1 + p2}
, param_dependent_02{(p1 * 1.5f)/param_dependent_01}
{}
auto get_pi1() { return param_independent_01; }
auto get_pi2() { return param_independent_02; }
auto get_pd1() { return param_dependent_01; }
auto get_pd2() { return param_dependent_02; }
friend std::ostream& operator<<(std::ostream& os, const Configuration& c) {
return os << fmt::format("pi1: {}\npi2: {}\npd1: {}\npd2: {}\n",
c.param_independent_01, c.param_independent_02,
c.param_dependent_01, c.param_dependent_02);
}
};
int main() {
constexpr Configuration c{3.14, 9.8};
std::cout << c;
}
Спасибо за ваш ответ. У вас есть идеи, как избежать огромного конструктора в ситуации, когда у меня меньше десятков независимых параметров?
Для конфигураций, требующих большого количества параметров, я бы рассмотрел их чтение из входного потока, обычно из файла, если вы хотите, чтобы пользователь мог контролировать эти параметры; хотя может случиться так, что источником этих параметров является другой процесс, поэтому можно использовать другой входной поток (сериализованный поток данных или что-то в этом роде).
Нет необходимости в классе с пользовательским конструктором, просто сделайте это:
struct Configuration
{
float param_independent_01 = 0; // Always initialize all class members.
float param_independent_02 = 0;
float param_dependent_01() const {return param_independent_01 + param_independent_02;}
float param_dependent_02() const {return 1.5f*param_independent_01/(param_independent_01 + param_independent_02);}
};
Или просто встроенные переменные constexpr в пространстве имен (можно поместить в заголовок). Это позволяет вам написать некоторые функции constexpr (consteval) для вычисления значений. (Не все должно быть классом или структурой)
// header file
#pragma once
namespace configuration
{
inline constexpr float get_param_dependent_02(const float p1, const float p2)
{
return (1.5f * p1) / (p1+p2);
}
inline constexpr float param_independent_01{ 0.236f };
inline constexpr float param_independent_02{ 0.728f };
inline constexpr float param_dependent_01 = param_independent_01 + param_independent_02; // direct
inline constexpr float param_dependent_02 = get_param_dependent_02(param_independent_01, param_independent_02); // or through constexpr/consteval function
};
int main()
{
float f = configuration::param_dependent_02;
}
Большое спасибо за ваш ответ. Пожалуйста, не могли бы вы сказать мне, почему необходимо использовать встроенный вместе с constexpr?
Так что это полезно в заголовочных файлах. isocpp.org/blog/2018/05/quick-q-use-of-constexpr-in-header-file
Зависимые параметры должны быть методами, которые возвращают результат вычисления с независимыми параметрами. Например:
float param_dependent_01() { return param_independent_01 + param_independent_02; }