Правильный способ сделать глобальную "константу" в C++

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

константы.h:

extern const pi;

константы.cpp:

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

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

мысли?

10 ответов


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

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


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

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


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

struct GlobalState {
  float get_x() const;

  float get_y() const;

  ...
};

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

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


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

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


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


моя типичная идиома для программ с настраиваемыми элементами заключается в создании одноэлементного класса с именем "конфигурация". Внутри configuration go вещи,которые могут быть прочитаны из проанализированных файлов конфигурации, реестра, переменных среды и т. д.

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


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

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

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

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

const gravity=configGravity();

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

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

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


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

Ну

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

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

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

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

глобалы не зло...

пришлось сначала снять это с груди:)

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

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

extern Constants C = { ...  };

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

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

я использовал тот факт, что локальные переменные (т. е. члены, параметры, функции-locals,..) превалируют над глобальными в нескольких случаях как "апекты для бедных":

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

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! */  }
    }
}

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


область Вызова против области источника

альтернативно, если вы / ваши разработчики больше в процедурном, а не в стиле thsn OO, вы можете использовать объем вызовов вместо объем источник, С глобальный стек значений, примерно:

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();
  // ...
}

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


расчет и пользователей Интерфейс

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

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


Анализ Размерностей

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