Надлежащий способ сделать глобальную “константу” в C++

Как правило, способ, которым я определил бы истинную глобальную константу (позволяет, говорит, пи) должен был бы поместить константу экстерна в заголовочный файл и определить константу в .cpp файле:

constants.h:

extern const pi;

constants.cpp:

#include "constants.h"
#include <cmath>
const pi=std::acos(-1.0);

Это работает отлично для истинных констант, таких как пи. Однако я ищу лучшую практику когда дело доходит до определения "константы" в этом, это останется постоянным от прогона программы до прогона программы, но может измениться, в зависимости от входного файла. Примером этого была бы гравитационная постоянная, которая зависит от используемых единиц. g определяется во входном файле, и я хотел бы, чтобы он был глобальным значением, которое может использовать любой объект. Я всегда слышал, что это - плохая практика, чтобы иметь непостоянный globals, поэтому в настоящее время у меня есть g, сохраненный в системном объекте, который затем передается всем объектам, которые это генерирует. Однако это кажется немного неуклюжим и твердым поддержать, когда количество объектов растет.

Мысли?

7
задан MarkD 6 July 2010 в 19:30
поделиться

10 ответов

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

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

3
ответ дан 7 December 2019 в 05:17
поделиться

Я понимаю, в каком затруднительном положении вы оказались, но боюсь, что вы, к сожалению, делаете это неправильно.

Модули не должны влиять на программу, если вы попытаетесь обработать несколько разных модулей в основе своей программы, вы серьезно пострадаете.

По идее, вы должны сделать что-то вроде этого:

       Parse Input
            |
 Convert into SI metric
            |
       Run Program
            |
Convert into original metric
            |
      Produce Output

Это обеспечит надежную изоляцию вашей программы от различных существующих метрик. Таким образом, если однажды вы каким-то образом добавите поддержку французской метрической системы 16 века, вы просто добавите для правильной настройки шагов Convert (адаптеров) и, возможно, немного ввода / вывода (для распознавать их и распечатывать правильно), но ядро ​​программы, то есть вычислительный блок, не будет затронуто новой функциональностью.

Теперь, если вы хотите использовать константу, которая не является такой постоянной (например, ускорение свободного падения на Земле, которое зависит от широты, долготы и высоты), вы можете просто передать ее в качестве аргументов, сгруппированных с другими константы.

class Constants
{
public:
  Constants(double g, ....);

  double g() const;

  /// ...
private:
  double mG;

  /// ...
};

Это можно было бы сделать Singleton , но это противоречит (спорной) идиоме внедрения зависимостей. Лично я отхожу от Singleton насколько это возможно, я обычно использую некоторый класс Context , который я передаю в каждом методе, что значительно упрощает тестирование методов независимо друг от друга.

2
ответ дан 7 December 2019 в 05:17
поделиться

Плохо иметь глобалы, которые меняют значение в течение всего времени выполнения.

Значение, которое устанавливается один раз при запуске (и остается "постоянным" после этого) - вполне приемлемое использование для глобала.

1
ответ дан 7 December 2019 в 05:17
поделиться

Вы можете использовать вариант вашего последнего подхода, создать класс "GlobalState", содержащий все эти переменные, и передать его всем объектам:

struct GlobalState {
  float get_x() const;

  float get_y() const;

  ...
};

struct MyClass {
  MyClass(GlobalState &s)
  {
    // get data from s here
    ... = s.get_x();
  }
};

Он избегает глобальных переменных, если вы этого не сделаете. Мне они нравятся, и он плавно растет по мере того, как требуется больше переменных.

1
ответ дан 7 December 2019 в 05:17
поделиться

Законное использование синглтонов!

Одноэлементный класс constants () с методом для установки единиц измерения?

1
ответ дан 7 December 2019 в 05:17
поделиться

Почему ваше текущее решение будет трудно поддерживать? Вы можете разделить объект на несколько классов по мере его роста (один объект для параметров моделирования, таких как ваша гравитационная постоянная, один объект для общей конфигурации и так далее)

.
0
ответ дан 7 December 2019 в 05:17
поделиться

Моя типичная идиома для программ с настраиваемыми элементами - создание одноэлементного класса с именем «конфигурация». Внутри конфигурации есть вещи, которые могут быть прочитаны из проанализированных файлов конфигурации, реестра, переменных среды и т. Д.

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

0
ответ дан 7 December 2019 в 05:17
поделиться

Это на самом деле напоминает книгу по метапрограммному программированию шаблонов C++ от Abrahams & Gurtovoy - Есть ли лучший способ управлять вашими данными, чтобы вы не получали плохих преобразований из ярдов в метры или из объема в длину, и, возможно, этот класс знает о гравитации как ускорении формы.

Также у вас уже есть хороший пример здесь, pi = результат какой-то функции...

const pi=std::acos(-1.0);

Так почему бы не сделать гравитацию результатом какой-то функции, которая просто случайно считывает это из файла?

const gravity=configGravity();

configGravity() {
 // open some file
 // read the data
 // return result
}

Проблема в том, что, поскольку глобальный управляется до вызова main, вы не можете предоставить входные данные в функцию - какой конфигурационный файл, что делать, если файл отсутствует или в нем нет g.

Поэтому, если вы хотите обрабатывать ошибки, вам нужно перейти к более поздней инициализации, синглтоны подходят для этого лучше.

0
ответ дан 7 December 2019 в 05:17
поделиться

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

Итак,

(1) Предлагается обертка вокруг глобальной информации, конструктор которой принимает ifstream или строку имени файла reference (следовательно, файл должен существовать до вызова конструктора и будет существовать после вызова деструктора);

(2) Предлагается глобальная переменная обертки. Вы можете, дополнительно, убедиться, что это единственный экземпляр этой обертки, в этом случае вам нужно сделать ее синглтоном, как было предложено. С другой стороны, это может и не понадобиться (вы можете быть согласны иметь несколько копий одной и той же информации, если только это информация только для чтения!

(3) Предлагает const getter из обертки. Таким образом, пример может выглядеть так:

#include <iostream>
#include <string>
#include <fstream>
#include <cstdlib>//for EXIT_FAILURE

using namespace std;

class GlobalsFromFiles
{
public:
    GlobalsFromFiles(const string& file_name)
    {
        //...process file:
        std::ifstream ginfo_file(file_name.c_str());
        if( !ginfo_file )
        {
            //throw SomeException(some_message);//not recommended to throw from constructors 
            //(definitely *NOT* from destructors)
            //but you can... the problem would be: where do you place the catcher? 
            //so better just display an error message and exit
            cerr<<"Uh-oh...file "<<file_name<<" not found"<<endl;
            exit(EXIT_FAILURE);
        }

        //...read data...
        ginfo_file>>gravity_;
        //...
    }

    double g_(void) const
    {
        return gravity_;
    }
private:
    double gravity_;
};

GlobalsFromFiles Gs("globals.dat");

int main(void)
{
    cout<<Gs.g_()<<endl;
    return 0;
}
0
ответ дан 7 December 2019 в 05:17
поделиться

Глобальные переменные не являются злом

Пришлось сначала снять это с плеч :)

Я бы поместил константы в struct, и сделал глобальный экземпляр этого:

struct Constants
{
   double g;
   // ...
};

extern Constants C = { ...  };

double Grav(double m1, double m2, double r) { return C.g * m1 * m2 / (r*r); }

(Короткие имена тоже подойдут, все ученые и инженеры так делают.....)

Я использовал тот факт, что локальные переменные (т.е.

Вы можете легко изменить метод на

double Grav(double m1, double m2, double r, Constants const & C = ::C) 
{ return C.g * m1 * m2 / (r*r); }  // same code! 

Вы можете создать

struct AlternateUniverse
{
    Constants C; 

    AlternateUniverse()
    {
       PostulateWildly(C);   // initialize C to better values
       double Grav(double m1, double m2, double r) { /* same code! */  }
    }
}

Идея заключается в том, чтобы написать код с наименьшими накладными расходами в случае по умолчанию, и сохранить реализацию, даже если универсальные константы изменятся.


Call Scope vs. Source Scope

В качестве альтернативы, если вы/ваши разработчики более склонны к процедурному, а не ОО стилю, вы можете использовать call scope вместо source scope, с глобальным стеком значений, примерно так:

std::deque<Constants> g_constants;

void InAnAlternateUniverse()
{
   PostulateWildly(C);    // 
   g_constants.push_front(C);
   CalculateCoreTemp();
   g_constants.pop_front();
} 


void CalculateCoreTemp()
{
  Constants const & C= g_constants.front();
  // ...
}

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


Вычисления против пользовательского интерфейса

Мы подходим к вашей исходной проблеме иначе: Все внутреннее представление, все постоянные данные используют базовые единицы СИ. Конвертация происходит на входе и выходе (например, даже если типичный размер - миллиметр, он всегда хранится как метр).

Я не могу сравнить, но для нас это работает очень хорошо.


Размерный анализ

В других ответах по крайней мере намекалось на размерный анализ, например, в соответствующей библиотеке Boost. Она может обеспечить корректность размерности и автоматизировать преобразования ввода/вывода.

0
ответ дан 7 December 2019 в 05:17
поделиться
Другие вопросы по тегам:

Похожие вопросы: