Что использовать вместо статических переменных - PullRequest
3 голосов
/ 28 января 2011

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

static const Helper h(params);

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

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

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

В том же C ++ FAQ упоминается эта опция:

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

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

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

Ответы [ 4 ]

5 голосов
/ 28 января 2011

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

Если вы опасаетесь, что у вас возникнет проблема с инициализацией, вам следует использовать подход 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 здесь поможет, но даже тогда техническая поддержка сингулярности вместо того, чтобы полагаться на поведение программистов, может быть очень раздражающей ... например, при написании тестов: действительно ли вы хотите выгружать / перезагружать вашу программу между каждым модульным тестомпросто изменить конфигурацию между каждым?Тьфу.Гораздо проще создать его один раз и поверить в своих коллег-программистов ... или в функциональные тесты;)

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

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

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

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


EDIT :

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

Таким образом, следующий код действителен в соответствии со стандартом:

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

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

2 голосов
/ 28 января 2011

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

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

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

0 голосов
/ 28 января 2011

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

0 голосов
/ 28 января 2011

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

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

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

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

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