Что использовать вместо статических переменных

StackOverflow https://stackoverflow.com/questions/4829547

  •  26-10-2019
  •  | 
  •  

Вопрос

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

static const Helper h(params);

Но потом возникает вот это статический порядок инициализации проблема, поэтому, если Helper ссылается на некоторую другую статику (через params), это может привести к UB.

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

Я думал о синглетонах, но это было бы излишеством из-за шаблонного кода и неудобного синтаксиса (например MySingleton::GetInstance().MyVar) - эти объекты являются помощниками, поэтому предполагается, что они упрощают вещи, а не усложняют их...

Тот же самый FAQ по C ++ упоминания этот вариант:

 Fred& x()
 {
   static Fred* ans = new Fred();
   return *ans;
 } 

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

Редактировать:Мне следовало бы пояснить, зачем мне на самом деле нужны эти помощники:они очень похожи на обычные константы и могли бы быть предварительно вычислены, но удобнее делать это во время выполнения.Я бы предпочел создавать их экземпляры перед main, поскольку это автоматически решает проблемы с многопоточностью (от которых локальная статика не защищена в C ++ 03).Кроме того, как я уже сказал, они часто ограничиваются единицей перевода, поэтому нет смысла экспортировать их и инициализировать в main().Вы можете думать о них просто как о константах, но известных только во время выполнения.

Это было полезно?

Решение

Существует несколько возможностей для глобального состояния (независимо от того, изменяемо оно или нет).

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

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

class Singleton
{
public:
  static void DoSomething(int i)
  {
    Singleton& s = Instance();
    // do something with i
  }


private:
  Singleton() {}
  ~Singleton() {}

  static Singleton& Instance()
  {
    static Singleton S; // no dynamic allocation, it's unnecessary
    return S;
  }
};

// Invocation
Singleton::DoSomething(i);

Другой дизайн в чем-то похож, хотя я предпочитаю его, потому что он намного упрощает переход к неглобальному дизайну.

class Monoid
{
public:
  Monoid()
  {
    static State S;
    state = &s;
  }

  void doSomething(int i)
  {
    state->count += i;
  }

private:
  struct State
  {
    int count;
  };

  State* state;
};


// Use
Monoid m;
m.doSomething(1);

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

Позвольте нам, будьте добры, подвергнуть сомнению дизайн:

  • вам действительно нужно применять сингулярность ?
  • вам действительно нужно, чтобы объект был построен раньше main начинается ?

Сингулярность, как правило, преувеличивается.Здесь поможет C ++ 0x, но даже тогда техническое обеспечение singularity вместо того, чтобы полагаться на поведение программистов, может быть очень раздражающим...например, при написании тестов:вы действительно хотите выгружать / перезагружать свою программу между каждым модульным тестированием только для того, чтобы изменить конфигурацию между каждым из них?Тьфу.Гораздо проще создать его один раз и верить в своих коллег-программистов...или функциональные тесты ;)

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

Это может показаться наивным, но на самом деле существует одна проблема с вычислениями во время загрузки библиотеки:как вы справляетесь с ошибками ?Если вы выбросите, библиотека не загрузится.Если вы не бросаете и продолжаете, вы находитесь в недопустимом состоянии.Не так уж и смешно, не так ли?Все становится намного проще, как только начинается реальная работа, потому что вы можете использовать обычную логику потока управления.

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

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


Редактировать:

Что касается вопросов порядка инициализации:объекты в пределах одной единицы перевода гарантированно будут инициализированы в том порядке, в котором они определены.

Следовательно, следующий код действителен в соответствии со стандартом:

static int foo() { return std::numeric_limits<int>::max() / 2; }
static int bar(int c) { return c*2; }

static int const x = foo();
static int const y = bar(x);

Порядок инициализации является проблемой только при обращении к константам / переменным, определенным в другой единице преобразования.Как таковой, static объекты, естественно, могут быть выражены без проблем, если они относятся только к static объекты в пределах одной единицы перевода.

Что касается космической проблемы:тот самый as-if правило может творить здесь чудеса.Неофициально as-if правило означает, что вы указываете поведение и предоставляете его предоставление компилятору / компоновщику / среде выполнения, не заботясь о том, как оно обеспечивается.Это то, что на самом деле обеспечивает оптимизацию.

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

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

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

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

Насколько мне известно, нет ничего "лучше" этих двух подходов к приложению.

Одиночные и глобальные объекты часто считаются злом.Самый простой и гибкий способ - создать экземпляр объекта в вашем main выполните функцию и передайте этот объект другим функциям:

void doSomething(const Helper& h);
int main() {
  const Parameters params(...);
  const Helper h(params);
  doSomething(h);
}

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

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

Делает Helper необходимость существовать до main бежит?Если нет, создайте (набор?) глобальный переменные указателя инициализирован как 0.Затем используйте main, чтобы заполнить их постоянным состоянием в определенном порядке.Если хотите, вы даже можете создать вспомогательные функции, которые выполняют разыменование за вас.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top