Правильный способ сделать глобальную «константу» в C ++ - PullRequest
6 голосов
/ 06 июля 2010

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

constants.h:

extern const pi;

constants.cpp:

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

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

Мысли?

Ответы [ 10 ]

3 голосов
/ 06 июля 2010

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

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

1 голос
/ 07 июля 2010

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

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

Концептуально, вы должны сделать что-то вроде этого:

       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, который я передаю в каждом методе, что значительно упрощает тестирование методов независимо друг от друга.

1 голос
/ 06 июля 2010

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

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

1 голос
/ 06 июля 2010

Вы можете использовать вариант вашего последнего подхода, создать класс "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 голос
/ 06 июля 2010

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

Константы одноэлементного класса () с методом для установки единиц измерения

0 голосов
/ 07 июля 2010

Глобалы не злые

Надо было сначала снять это с моей груди:)

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

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

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


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

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

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 Library . Он может обеспечить корректность размеров и автоматизировать преобразования ввода / вывода.

0 голосов
/ 07 июля 2010

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

Ну,

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

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

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

#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 голосов
/ 07 июля 2010

Это на самом деле напоминает книгу по метапрограммированию шаблонов 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 голосов
/ 06 июля 2010

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

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

0 голосов
/ 06 июля 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...