Подтвердить что ты не робот

С++ Nifty Counter idiom; Зачем?

Недавно я встретил Nifty Counter Idiom. Я понимаю, что это используется для реализации глобальных переменных в стандартной библиотеке, такой как cout, cerr и т.д. Поскольку эксперты выбрали ее, я предполагаю, что это очень сильный метод.

Я пытаюсь понять, в чем преимущество, используя нечто большее, чем Meyer Singleton.

Например, в файле заголовка можно просто:

inline Stream& getStream() { static Stream s; return s; }
static Stream& stream = getStream();

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

  • Не гарантируется ли наличие единого глобального объекта в общих и статических библиотеках? Похоже, ODR должен гарантировать, что может быть только одна статическая переменная.
  • Есть ли какая-то стоимость исполнения? Кажется, что и в моем коде, и в Nifty Counter вы следуете одной ссылке, чтобы добраться до объекта.
  • Есть ли ситуации, когда подсчет ссылок действительно полезен? Похоже, что это все равно приведет к построению объекта, если заголовок включен и уничтожен на конце программы, например, Meyer Singleton.
  • Отвечает ли ответ на что-то вручную? У меня нет слишком большого опыта.

Изменить: мне было предложено написать следующий бит кода во время чтения ответа Якка, я добавлю его к исходному вопросу в виде быстрой демонстрации. Это очень минимальный пример, показывающий, как использование Meyer Singleton + глобальной ссылки приводит к инициализации до main: http://coliru.stacked-crooked.com/a/a7f0c8f33ba42b7f.

4b9b3361

Ответ 1

Статическая локальная /Meyer singleton + статическая глобальная ссылка (ваше решение) почти эквивалентна отличному счетчику.

Различия заключаются в следующем:

  • В вашем решении не требуется файл .cpp.

  • Технически static Steam& существует в каждом модуле компиляции; на объект, на который ссылается, нет. Поскольку нет способа обнаружить это в текущей версии С++, по мере того, как это уходит. Но некоторые реализации могут фактически создать эту ссылку вместо того, чтобы ее ускорить.

  • Кто-то может вызвать getStream() до созданного static Stream&; это вызовет трудности в порядке уничтожения (когда поток будет уничтожен позже, чем ожидалось). Этого можно избежать, сделав это против правил.

  • В стандарте предусмотрено создание локального static Stream в потоковом потоке inline getStream. Обнаружение того, что этого не произойдет, является сложным для компилятора, поэтому в вашем решении могут существовать избыточные потоки безопасности потоков. Отличный счетчик явно не поддерживает поточную безопасность; это считается безопасным, поскольку он работает при статическом времени инициализации, до того, как ожидаются потоки.

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

  • "волшебная статика" (статические локали без условий гонки), где введено в С++ 11. Могут возникнуть другие проблемы до магической статики С++ 11 с вашим кодом; единственный, о котором я могу думать, - это кто-то, вызывающий getStream() непосредственно в другом потоке во время статической инициализации, который (как упоминалось выше) должен быть вообще запрещен.

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

Иногда бывает важно избежать более одного синглтона.

Ответ 2

Подведение итогов ответов и комментариев:

Позвольте сравнить 3 разных варианта библиотеки, желающих представить глобальный Singleton, как переменную или через функцию getter:

Вариант 1 - отличный шаблон шаблона, позволяющий использовать глобальная переменная, которая:

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

Вариант 2 - синглетный синтаксис Meyers с ссылочной переменной (как указано в вопросе):

  • уверен, что создан
  • заверил, что будет создан до первого использования

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


Вариант 3 - шаблон сингла Meyers без ссылочной переменной (вызов getter для извлечения объекта Singleton):

  • уверен, что создан
  • заверил, что будет создан до первого использования
  • гарантированно создается по всем общим объектам , динамически связанным с библиотекой, создающей этот Singleton.

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

Эта опция будет выглядеть так:

// libA .h
struct A {
    A();
};

A& getA();

// some other header
A global_a2 = getA();

// main
int main() {
    std::cerr << "main\n";
}

// libA .cpp - need to be dynamically linked! (same as libstdc++ is...)
// thus the below shall be created only once in the process
A& getA() {
    static A a;
    return a;
} 

A::A() { std::cerr << "construct A\n"; }

Ответ 3

Все ваши вопросы об утилите/исполнении Nifty Counter aka Schwartz Counter в основном отвечал Максим Егорушкин в этом ответе (но также см. также комментарии).

Глобальные переменные в современном С++

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

Однако, в одиночном режиме Meyer, каждый раз, когда вы хотите получить доступ к глобальному объекту, вы должны проверить, является ли он нулевым, или компилятор испускает код, который проверяет, была ли статическая переменная уже построена до того, как будет предпринята попытка доступа. В Nifty Counter у вас уже есть указатель, и вы просто убираете его, так как вы можете предположить, что init произошел во время запуска.

Итак, Nifty Counter vs. Meyer singleton - это в основном компромисс между временем запуска программы и временем выполнения.

Ответ 4

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

#include <iostream>

struct use_std_out_in_ctor
{
    use_std_out_in_ctor()
    {
        // std::cout guaranteed to be initialized even if this
        // ctor runs during static initialization
        std::cout << "Hello world" << std::endl;
    }
};

use_std_out_in_ctor global; // causes ctor to run during static initialization

int main()
{
    std::cout << "Did it print Hello world?" << std::endl;
}