Как иметь статические элементы данных в библиотеке только для заголовков?

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

Скажем, я хочу предоставить этот класс:

class i_want_a_static_member
{
    static expensive_resource static_resource_;

public:
    void foo()
    {
        static_resource_.bar();
    }
};

Затем пользователь класса не должен забыть где-то определить статический член (как уже ответил многие раз):

// this must be done somewhere in a translation unit
expensive_resource i_want_a_static_member::static_resource_;

У меня есть ответ ниже, но у него есть некоторые недостатки. Есть ли лучшие и / или более элегантные решения?


person pesche    schedule 29.07.2012    source источник
comment
Когда вы говорите «без шаблонов», вы имеете в виду, что вас заставляют не использовать какие-либо шаблоны, или просто основные классы не являются шаблонными?   -  person Vaughn Cato    schedule 29.07.2012
comment
@VaughnCato Я просто не хочу, чтобы пользователь класса имел дело с шаблонным классом. Возможно, просто нет смысла вводить параметр шаблона для класса i_want_a_static_member.   -  person pesche    schedule 29.07.2012
comment
Хорошо, но если это вспомогательный класс, с которым пользователю не нужно иметь дело, то можно ли его использовать по шаблону?   -  person Vaughn Cato    schedule 29.07.2012
comment
@VaughnCato Да, все в порядке. В моем собственном ответе вы видите, что я тоже использую шаблонный вспомогательный класс. Но я хочу предоставить класс, с которым пользователь может иметь дело (это причина его предоставления).   -  person pesche    schedule 29.07.2012
comment
Да я вижу. Еще вы можете использовать встроенную функцию-член со статической локальной переменной, и функция-член просто возвращает ссылку на нее.   -  person Vaughn Cato    schedule 29.07.2012
comment
Настоящий вопрос: почему только заголовок? Это просто источник проблем для вас (поскольку вы собираетесь получать отчеты об ошибках для компиляторов, о которых вы никогда не слышали) и для клиента (поскольку он увидит, что время его компиляции серьезно увеличится).   -  person James Kanze    schedule 29.07.2012
comment
@JamesKanze Только заголовок или нет - это сложный вопрос. В моем случае библиотека используется только дюжиной штатных программистов с тремя компиляторами, к каждому из которых у меня есть доступ. Что касается только заголовка: для одного из наших компиляторов пока нет официального порта Boost. Хотя части Boost, предназначенные только для заголовков, обычно работают «из коробки», использование остальной части Boost потребует значительных усилий, поэтому, возможно, вы понимаете, почему мне нравятся библиотеки только для заголовков, несмотря на их недостатки. Хотя для приведенного выше примера, возможно, просто лень оставаться только заголовком.   -  person pesche    schedule 30.07.2012
comment
@pesche Заставить весь Boost работать - проблема, если никто другой не сделал этого за вас. Но вам не обязательно быть таким сложным, как Boost. Если у вас есть только несколько целевых систем, к которым у вас есть доступ к компиляторам, вам не должно быть слишком сложно доставить скомпилированные библиотеки. В конце концов, вы все равно создаете библиотеки для того, чтобы протестировать их.   -  person James Kanze    schedule 31.07.2012


Ответы (3)


C ++ 17 и выше

Используйте inline static переменные для нединамической инициализации:

struct Foo
{
    inline static int I = 0;
};

И в противном случае используйте локальные статические переменные функции:

struct Foo
{
    static std::string& Bar()
    {
        static std::string S = compute();
        return S;
    }
};

C ++ 14 и ниже

Используйте функцию локальной статики, так как она проще в использовании.

Если по какой-то причине вы действительно хотите статический член данных, вы можете использовать трюк с шаблоном:

template <typename T = void>
struct Foo
{
     static int I = 0; // inline initialization only for simple types.
};

template <typename T>
int Foo<T>::I;

О местной статике

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

Порядок, в котором статика области видимости файла или класса динамически инициализируется, как правило, не определен, что приводит к Fiasco порядка статической инициализации, когда вы пытаетесь прочитать неинициализированный статический объект как часть инициализации другого. Локальная статика решает проблему за счет ленивой инициализации при первом использовании.

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

person Matthieu M.    schedule 29.07.2012
comment
@SrinathSridhar: Нечего объяснять, это просто особенность C ++. Когда переменная в области функции объявляется с квалификатором хранилища static, тогда создается язык, на котором создается один и только один экземпляр. Этот экземпляр детерминированно инициализируется ленивой инициализацией в первый раз, когда управление потоком проходит через его объявление. - person Matthieu M.; 12.08.2013
comment
Это не является потокобезопасным до спецификации C ++ 11. Даже сейчас, когда спецификация отсутствует, не все компиляторы поддерживают потокобезопасную статическую инициализацию. Например, ни MS Visual Studio 2012, ни 2013 не поддерживают то, что они называют магической статикой. - person Micah Zoltu; 19.08.2013
comment
@MicahCaldwell: Спасибо за замечание, я давно не использую Visual Studio. Это весьма прискорбно, поскольку в gcc он долгое время был потокобезопасным (даже до C ++ 11). - person Matthieu M.; 19.08.2013
comment
@prehistoricpenguin: если инициализация динамическая (не constexpr), то она происходит при первом использовании переменной. Это отлично подходит для более быстрого запуска двоичных файлов, но может вызвать увеличение задержки при первом вызове и обнаружение ошибок задержки. - person Matthieu M.; 21.05.2019

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

template <typename T>
struct static_holder
{
    static T static_resource_;
};

template <typename T>
T static_holder<T>::static_resource_;

Теперь используйте класс держателя:

class expensive_resource { /*...*/ };

class i_want_a_static_member : private static_holder<expensive_resource>
{
public:
    void foo()
    {
        static_resource_.bar();
    }
};

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

person pesche    schedule 29.07.2012
comment
Примечание: на самом деле вы можете использовать композицию вместо наследования. struct A { static_holder<B> x; static_holder<B> y; }; будет работать, хотя не будет использовать оптимизацию пустой базы. - person Matthieu M.; 13.08.2013
comment
Это не приводит к тем же небезопасным проблемам потока, что и решение функции local static (magic statics) выше, верно? - person solstice333; 12.06.2019

Начиная с C ++ 17. Теперь для этого можно использовать встроенные переменные:

static const inline float foo = 1.25f;
person redfeatherplusplus    schedule 13.11.2018
comment
СПАСИБО! От имени пуристов заголовков во всем мире! - person Syndog; 21.12.2018
comment
Обратите внимание, что static inline создает разные переменные в разных единицах перевода. inline (без static) создает настоящую глобальную переменную. См. en.cppreference.com/w/cpp/language/inline. Также я бы исключил const из этого ответа, поскольку это особый случай. - person ARA1307; 25.12.2018
comment
Не могли бы вы уточнить, где вы получаете Note, static inline создает разные переменные в разных единицах перевода. inline (без static) создает настоящую глобальную переменную. из вашей ссылки cppreference? Я бегло просмотрел это и не видел, откуда это взялось. - person redfeatherplusplus; 04.01.2019
comment
извините, но я думаю, что обновление до C ++ 17 нецелесообразно в 99% случаев. Тем не менее, хорошая мысль. - person fiorentinoing; 08.02.2019