Как определить структуру С++ для конфигурации?

Я пытался решить следующую проблему на С++. Я хотел бы определить структуру, содержащую параметры конфигурации для некоторого программного модуля. Параметры конфигурации в основном представляют собой значения с плавающей запятой и бывают двух типов:

  • параметры, которые являются независимыми, т. е. их значения задаются непосредственно некоторыми числами с плавающей запятой
  • параметры, которые являются зависимыми, то есть их значения задаются некоторыми выражениями, где операнды являются независимыми параметрами

Вот пример

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)
};

Я предполагаю, что структура конфигурации будет создана только один раз, а значения параметров известны во время компиляции. Может ли кто-нибудь дать мне совет, как это сделать на С++?

Зависимые параметры должны быть методами, которые возвращают результат вычисления с независимыми параметрами. Например: float param_dependent_01() { return param_independent_01 + param_independent_02; }

wohlstad 08.02.2023 15:50

Должны быть методы, а не могут быть методы.

Michaël Roy 08.02.2023 15:51

@MichaëlRoy согласен, отредактировано.

wohlstad 08.02.2023 15:51

@wohlstad большое спасибо за вашу реакцию. Я вижу в основном два недостатка в вашей идее. Во-первых, независимые и зависимые параметры будут использоваться по-разному (доступ к переменным и вызов метода). Второй заключается в том, что зависимые параметры будут пересчитываться снова и снова, несмотря на то, что их значения не меняются (они известны во время компиляции).

Steve 08.02.2023 16:00

@ Стив, это зависит от твоей системы. Простые и относительно нечастые вычисления могут быть лучше, чем хранение избыточных данных (и риск ошибок из-за несогласованности). Но если это критично в вашем случае - опубликованный ответ предлагает способ сделать это.

wohlstad 08.02.2023 16:04
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
5
78
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Одним из подходов к достижению такого поведения является использование списка инициализации конструктора 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;
}

Благодарю за ваш ответ. У меня довольно много параметров (нижние десятки). В таком случае это будет означать огромный конструктор, который кажется мне очень подверженным ошибкам.

Steve 08.02.2023 16:07

Если вы знаете, что конфигурация не изменится во время выполнения, вы можете реализовать конструктор 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;
}

Спасибо за ваш ответ. У вас есть идеи, как избежать огромного конструктора в ситуации, когда у меня меньше десятков независимых параметров?

Steve 08.02.2023 16:13

Для конфигураций, требующих большого количества параметров, я бы рассмотрел их чтение из входного потока, обычно из файла, если вы хотите, чтобы пользователь мог контролировать эти параметры; хотя может случиться так, что источником этих параметров является другой процесс, поэтому можно использовать другой входной поток (сериализованный поток данных или что-то в этом роде).

rturrado 08.02.2023 16:16

Нет необходимости в классе с пользовательским конструктором, просто сделайте это:

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?

Steve 08.02.2023 16:24

Так что это полезно в заголовочных файлах. isocpp.org/blog/2018/05/quick-q-use-of-constexpr-in-header-f‌​ile

Pepijn Kramer 08.02.2023 16:55

Другие вопросы по теме