статические конструкторы в C ++? Мне нужно инициализировать частные статические объекты


176

Я хочу иметь класс с закрытым статическим членом данных (вектор, который содержит все символы az). В Java или C # я могу просто создать «статический конструктор», который будет запускаться до того, как я создам экземпляры класса, и установить статические члены-данные класса. Он запускается только один раз (поскольку переменные доступны только для чтения и должны быть установлены только один раз), и поскольку он является функцией класса, он может получить доступ к своим закрытым членам. Я мог бы добавить код в конструктор, который проверяет, инициализирован ли вектор, и инициализировать его, если это не так, но это вводит много необходимых проверок и не кажется оптимальным решением проблемы.

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

Можно ли иметь частные статические члены-данные в классе, если я не хочу инициализировать их в конструкторе экземпляра?



1
@CiroSantilli question 改造 中心 六四 事件 法轮功 Этот вопрос посвящен запуску кода для инициализации частных статических объектов , а не установке постоянных значений частных статических примитивных типов. Решения разные.
Гордон Густафсон

Ах, я думаю, что вы правы, отказываясь.
Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功

Ответы:


180

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

class StaticStuff
{
     std::vector<char> letters_;

public:
     StaticStuff()
     {
         for (char c = 'a'; c <= 'z'; c++)
             letters_.push_back(c);
     }

     // provide some way to get at letters_
};

class Elsewhere
{
    static StaticStuff staticStuff; // constructor runs once, single instance

};

12
Спасибо! хотя это очень раздражает, когда приходится все это делать. Одна из многих "ошибок" C # и Java, извлеченных из.
Гордон Густафсон

109
Да. Я всегда указываю людям, что если бы C ++ не допустил всех этих «ошибок», то другие языки должны были бы их совершить. C ++, покрывающий так много основ, даже совершающий ошибки, отлично подходит для языков, которые следуют за ним.
Quark

11
Только один маленький нюанс, поскольку конструкторы вступают в игру, никто не гарантирует, когда конструктор для статического объекта выполняется. Хорошо известный намного более безопасный подход - это класс Elsewhere {StaticStuff & get_staticStuff () {static StaticStuff staticStuff; // конструктор запускается один раз, когда кому-то это нужно сначала return staticStuff; }}; Интересно, могут ли статические конструкторы в C # и Java обеспечить ту же гарантию, что и код выше ...
Олег Жилин

13
@ Олег: Да, они делают. Стандарт гарантирует, что конструкторы для всех нелокальных переменных выполняются перед вводом main. Он также гарантирует, что в модуле компиляции порядок построения четко определен и соответствует порядку объявления в модуле компиляции. К сожалению, они не определяют порядок для нескольких модулей компиляции.
Мартин Йорк,

13
Это на самом деле тот случай, когда friendимеет большой смысл, так что класс Elsewhereможет легко получить доступ StaticStuffк внутренним объектам (не нарушая инкапсуляцию каким-либо опасным способом, я мог бы добавить).
Конрад Рудольф

81

Ну, вы можете иметь

class MyClass
{
    public:
        static vector<char> a;

        static class _init
        {
          public:
            _init() { for(char i='a'; i<='z'; i++) a.push_back(i); }
        } _initializer;
};

Не забудьте (в .cpp) это:

vector<char> MyClass::a;
MyClass::_init MyClass::_initializer;

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


+1 (не пробовал) Но: когда вызывается ctor _init._init ()? До или после ctor MyClass, когда у меня есть статический объект MyClass? Я думаю, вы не можете сказать ...
Ур.

2
привет, где я могу найти больше об этой магии "инициализатора"?
Карел Билек

Разве это не должно быть MyClass::a.push_back(i)вместо a.push_back(i)?
Нил Басу

4
@ur .: _initializerявляется подобъектом MyClass. Субобъекты инициализируются в следующем порядке: виртуальные субобъекты базового класса, в порядке глубины, слева направо (но инициализируют каждый отдельный подобъект только один раз); затем подобъекты простого базового класса в порядке глубины слева направо; затем член подобъектов в порядке объявления. Таким образом, безопасно использовать стратегию EFraim, при условии, что код _initialiserотносится только к членам, объявленным до него.
j_random_hacker

2
Этот ответ лучше принятого, поскольку автор упомянул об обязательной инициализации во втором фрагменте кода.
Джефф Т.

33

Решение C ++ 11

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

Заголовочный файл:

class MyClass {
    static const vector<char> letters;
    static const size_t letterCount;
};

Исходный файл:

// Initialize MyClass::letters by using a lambda expression.
const vector<char> MyClass::letters = [] {
    vector<char> letters;
    for (char c = 'a'; c <= 'z'; c++)
        letters.push_back(c);
    return letters;
}();

// The initialization order of static members is defined by the order of
// definition within the source file, so we can access MyClass::letters here.
const size_t MyClass::letterCount = letters.size();

интересное решение. в этом случае, если я выброшу исключение, кто может поймать его?
Рафи Винер

5
Статический код инициализации программы никогда не должен выдавать никаких исключений, иначе программа будет аварийно завершена. Вы должны обернуть логику инициализатора в try catchблок, если могут быть сгенерированы исключения.
emkey08

19

В файле .h:

class MyClass {
private:
    static int myValue;
};

В файле .cpp:

#include "myclass.h"

int MyClass::myValue = 0;

5
Это прекрасно работает для отдельных статических членов (независимо от типа). Недостаток по сравнению со статическими конструкторами заключается в том, что вы не можете наложить порядок между различными статическими членами. Если вам нужно это сделать, см. Ответ Earwicker.
кварк

Я делаю именно это, но он все еще не компилируется. И это говорит, что это проблемная область (в конструкторе, а не в заголовке)
Flotolk

14

Вот еще один подход, аналогичный Даниэлю Эрвикеру, также использующий предложение класса от друга Конрада Рудольфа. Здесь мы используем внутренний приватный служебный класс друга для инициализации статических членов вашего основного класса. Например:

Заголовочный файл:

class ToBeInitialized
{
    // Inner friend utility class to initialize whatever you need

    class Initializer
    {
    public:
        Initializer();
    };

    friend class Initializer;

    // Static member variables of ToBeInitialized class

    static const int numberOfFloats;
    static float *theFloats;

    // Static instance of Initializer
    //   When this is created, its constructor initializes
    //   the ToBeInitialized class' static variables

    static Initializer initializer;
};

Файл реализации:

// Normal static scalar initializer
const int ToBeInitialized::numberOfFloats = 17;

// Constructor of Initializer class.
//    Here is where you can initialize any static members
//    of the enclosing ToBeInitialized class since this inner
//    class is a friend of it.

ToBeInitialized::Initializer::Initializer()
{
    ToBeInitialized::theFloats =
        (float *)malloc(ToBeInitialized::numberOfFloats * sizeof(float));

    for (int i = 0; i < ToBeInitialized::numberOfFloats; ++i)
        ToBeInitialized::theFloats[i] = calculateSomeFancyValue(i);
}

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


+1 За пример, который хранит реализацию в своем собственном файле.
Эндрю Ларссон

1
Кроме того, вы должны убедиться, что он ToBeInitialized::Initializer::Initializer()вызывается, поэтому вам нужно добавить ToBeInitialized::Initializer ToBeInitialized::initializer;его в файл реализации. Я взял кое-что из твоей идеи и идеи Эфрейма, и это работает именно так, как мне нужно, и выглядит чисто. Спасибо чувак.
Эндрю Ларссон

11

Test::StaticTest() вызывается ровно один раз при глобальной статической инициализации.

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

static_constructor<&Test::StaticTest>::c;вызывает инициализацию cво время глобальной статической инициализации.

template<void(*ctor)()>
struct static_constructor
{
    struct constructor { constructor() { ctor(); } };
    static constructor c;
};

template<void(*ctor)()>
typename static_constructor<ctor>::constructor static_constructor<ctor>::c;

/////////////////////////////

struct Test
{
    static int number;

    static void StaticTest()
    {
        static_constructor<&Test::StaticTest>::c;

        number = 123;
        cout << "static ctor" << endl;
    }
};

int Test::number;

int main(int argc, char *argv[])
{
    cout << Test::number << endl;
    return 0;
}

Это фантастическое решение. Мне также очень нравится ответ Дугласа Манделя , но он еще более лаконичен.
FlintZA

Это действительно потрясающе!
nh_

9

Нет необходимости в init()функции, std::vectorможет быть создан из диапазона:

// h file:
class MyClass {
    static std::vector<char> alphabet;
// ...
};

// cpp file:
#include <boost/range.hpp>
static const char alphabet[] = "abcdefghijklmnopqrstuvwxyz";
std::vector<char> MyClass::alphabet( boost::begin( ::alphabet ), boost::end( ::alphabet ) );

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

C ++ 11 Обновление

Начиная с C ++ 11, вы можете сделать это вместо этого:

// cpp file:
std::vector<char> MyClass::alphabet = { 'a', 'b', 'c', ..., 'z' };

Это семантически эквивалентно решению C ++ 98 в исходном ответе, но вы не можете использовать строковый литерал справа, поэтому он не является полностью превосходящим. Однако, если у Вас есть вектор любого другого типа , чем char, wchar_t, char16_tили char32_t(массивы из которых может быть записаны в виде строковых литералов), версия C ++ 11 будет строго удалить шаблонный код без введения другого синтаксиса, по сравнению с C ++ 98 версия.


Мне это нравится. Хотя, если бы мы могли сделать это в одну строку без теперь бесполезного алфавита.
Мартин Йорк

Для возникновения проблем с библиотеками, имеет ли значение, является ли статический класс закрытым или общедоступным? Кроме того, имеет ли значение, является ли библиотека статической (.a) или динамической (.so)?
Захари Краус

@ZacharyKraus: что такое публичный / частный класс ? И нет, хотя проблемы разные, но перекрываются, не имеет значения, связана ли библиотека статически или динамически.
Марк Мутц - Ммуц

@ MarcMutz-mmutz Извините за использование публичного / частного класса, который не является правильной терминологией C ++. То, что я имел в виду, является решением EFraim выше. В моей версии я сделал статический член класса закрытым. Я пытался понять, влияет ли наличие статического члена класса как открытого или закрытого в разработке и удобстве использования библиотеки. Моя интуиция говорит мне, что это не должно влиять на библиотеку, потому что пользователи никогда не будут иметь доступа ни к статическому члену класса, ни к объекту, который его строит, но я хотел бы получить мудрость гуру в этой теме.
Захари Краус

@ZacharyKraus: Основная проблема со статикой, которая требует динамической инициализации ([basic.start.init] / 2), заключается в том, что они запускают код. В библиотеках может быть так, что код библиотеки уже выгружен при запуске деструкторов. Если вы хотите услышать больше, я предлагаю опубликовать вопрос об этом.
Марк Мутц - Ммуц

6

Концепция статических конструкторов была введена в Java после того, как они извлекли уроки из проблем в C ++. Так что у нас нет прямого эквивалента.

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

//header

class A
{
    // Make sure this is private so that nobody can missues the fact that
    // you are overriding std::vector. Just doing it here as a quicky example
    // don't take it as a recomendation for deriving from vector.
    class MyInitedVar: public std::vector<char>
    {
        public:
        MyInitedVar()
        {
           // Pre-Initialize the vector.
           for(char c = 'a';c <= 'z';++c)
           {
               push_back(c);
           }
        }
    };
    static int          count;
    static MyInitedVar  var1;

};


//source
int            A::count = 0;
A::MyInitedVar A::var1;

4

При попытке скомпилировать и использовать класс Elsewhere(из ответа Уорикера ) я получаю:

error LNK2001: unresolved external symbol "private: static class StaticStuff Elsewhere::staticStuff" (?staticStuff@Elsewhere@@0VStaticStuff@@A)

Кажется, невозможно инициализировать статические атрибуты нецелых типов, не помещая некоторый код вне определения класса (CPP).

Для компиляции вы можете использовать « статический метод со статической локальной переменной внутри ». Что-то вроде этого:

class Elsewhere
{
public:
    static StaticStuff& GetStaticStuff()
    {
        static StaticStuff staticStuff; // constructor runs once, single instance
        return staticStuff;
    }
};

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

Уго Гонсалес Кастро.


Хотя будьте осторожны при использовании потоков. Я считаю, что в GCC конструирование статических локальных объектов защищено от одновременного выполнения, но в Visual C ++ это не так.
Даниэль Уорвикер

1
Начиная с C ++ 11 и в POSIX, он должен быть потокобезопасным.
Марк Мутц - Ммуц

Мне очень понравились два других решения выше ( это и это ), но ваше единственное, которое обеспечивает инициализацию статики в том порядке, в котором они необходимы для библиотек. У меня просто есть частный статический метод Instance, как у вас выше, и я обертываю доступ к другим значениям в общедоступных статических методах доступа, которые используют этот метод Instance вместо прямых ссылок. Спасибо.
FlintZA

Потрясающие! Это завершает это.
Гейб Халсмер

4

Я думаю, простое решение этого будет:

    //X.h
    #pragma once
    class X
    {
    public:
            X(void);
            ~X(void);
    private:
            static bool IsInit;
            static bool Init();
    };

    //X.cpp
    #include "X.h"
    #include <iostream>

    X::X(void)
    {
    }


    X::~X(void)
    {
    }

    bool X::IsInit(Init());
    bool X::Init()
    {
            std::cout<< "ddddd";
            return true;
    }

    // main.cpp
    #include "X.h"
    int main ()
    {
            return 0;
    }

Я тоже так делаю.
Etherealone

1

Просто решил тот же трюк. Мне пришлось указать определение одного статического члена для Singleton. Но все усложняется - я решил, что не хочу вызывать ctor из RandClass (), если только не собираюсь его использовать ... вот почему я не хотел глобально инициализировать синглтон в моем коде. Также я добавил простой интерфейс в моем случае.

Вот окончательный код:

Я упростил код и использовал функцию rand () и ее инициализатор с одиночным семенем srand ()

interface IRandClass
{
 public:
    virtual int GetRandom() = 0;
};

class RandClassSingleton
{
private:
  class RandClass : public IRandClass
  {
    public:
      RandClass()
      {
        srand(GetTickCount());
      };

     virtual int GetRandom(){return rand();};
  };

  RandClassSingleton(){};
  RandClassSingleton(const RandClassSingleton&);

  // static RandClass m_Instance;

  // If you declare m_Instance here you need to place
  // definition for this static object somewhere in your cpp code as
  // RandClassSingleton::RandClass RandClassSingleton::m_Instance;

  public:

  static RandClass& GetInstance()
  {
      // Much better to instantiate m_Instance here (inside of static function).
      // Instantiated only if this function is called.

      static RandClass m_Instance;
      return m_Instance;
  };
};

main()
{
    // Late binding. Calling RandClass ctor only now
    IRandClass *p = &RandClassSingleton::GetInstance();
    int randValue = p->GetRandom();
}
abc()
{
    IRandClass *same_p = &RandClassSingleton::GetInstance();
}

1

Вот мой вариант решения EFraim; Разница в том, что благодаря неявной реализации шаблона статический конструктор вызывается только в том случае, если создаются экземпляры класса, и что определение в.cpp файле не требуется (благодаря магии создания шаблона).

В .hфайле у вас есть:

template <typename Aux> class _MyClass
{
    public:
        static vector<char> a;
        _MyClass() {
            (void) _initializer; //Reference the static member to ensure that it is instantiated and its initializer is called.
        }
    private:
        static struct _init
        {
            _init() { for(char i='a'; i<='z'; i++) a.push_back(i); }
        } _initializer;

};
typedef _MyClass<void> MyClass;

template <typename Aux> vector<char> _MyClass<Aux>::a;
template <typename Aux> typename _MyClass<Aux>::_init _MyClass<Aux>::_initializer;

В .cppфайле вы можете иметь:

void foobar() {
    MyClass foo; // [1]

    for (vector<char>::iterator it = MyClass::a.begin(); it < MyClass::a.end(); ++it) {
        cout << *it;
    }
    cout << endl;
}

Обратите внимание, что MyClass::aон инициализируется, только если есть строка [1], потому что это вызывает (и требует создания экземпляра) конструктор, который затем требует создания экземпляра _initializer.


1

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

#include <iostream>
#include <vector>
using namespace std;

namespace {
  vector<int> vec;

  struct I { I() {
    vec.push_back(1);
    vec.push_back(3);
    vec.push_back(5);
  }} i;
}

int main() {

  vector<int>::const_iterator end = vec.end();
  for (vector<int>::const_iterator i = vec.begin();
       i != end; ++i) {
    cout << *i << endl;
  }

  return 0;
}

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

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

1

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

Файл .hpp:

vector<char> const & letters();

Файл .cpp:

vector<char> const & letters()
{
  static vector<char> v = {'a', 'b', 'c', ...};
  return v;
}


0

Вы определяете статические переменные-члены аналогично тому, как вы определяете методы-члены.

foo.h

class Foo
{
public:
    void bar();
private:
    static int count;
};

foo.cpp

#include "foo.h"

void Foo::bar()
{
    // method definition
}

int Foo::count = 0;

2
Вопрос CrazyJugglerDrummer был не о статически простом старом типе данных :)
2013 г.

0

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

//Foo.h
class Foo
{
 private:
  static int hello;
};


//Foo.cpp
int Foo::hello = 1;

Вопрос CrazyJugglerDrummer был не о статически простом старом типе данных :)
2013 г.

0

Как насчет создания шаблона для имитации поведения C #.

template<class T> class StaticConstructor
{
    bool m_StaticsInitialised = false;

public:
    typedef void (*StaticCallback)(void);

    StaticConstructor(StaticCallback callback)
    {
        if (m_StaticsInitialised)
            return;

        callback();

        m_StaticsInitialised = true;
    }
}

template<class T> bool StaticConstructor<T>::m_StaticsInitialised;

class Test : public StaticConstructor<Test>
{
    static std::vector<char> letters_;

    static void _Test()
    {
        for (char c = 'a'; c <= 'z'; c++)
            letters_.push_back(c);
    }

public:
    Test() : StaticConstructor<Test>(&_Test)
    {
        // non static stuff
    };
};

0

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

#include <iostream>

class MyClass 
{

    static const char * const letters(void){
        static const char * const var = "abcdefghijklmnopqrstuvwxyz";
        return var;
    }

    public:
        void show(){
            std::cout << letters() << "\n";
        }
};


int main(){
    MyClass c;
    c.show();
}

0

Это решение?

class Foo
{
public:
    size_t count;
    Foo()
    {
        static size_t count = 0;
        this->count = count += 1;
    }
};

0

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

class ClassStatic{
private:
    static char *str;
public:
    char* get_str() { return str; }
    void set_str(char *s) { str = s; }
    // A nested class, which used as static constructor
    static class ClassInit{
    public:
        ClassInit(int size){ 
            // Static constructor definition
            str = new char[size];
            str = "How are you?";
        }
    } initializer;
};

// Static variable creation
char* ClassStatic::str; 
// Static constructor call
ClassStatic::ClassInit ClassStatic::initializer(20);

int main() {
    ClassStatic a;
    ClassStatic b;
    std::cout << "String in a: " << a.get_str() << std::endl;
    std::cout << "String in b: " << b.get_str() << std::endl;
    a.set_str("I am fine");
    std::cout << "String in a: " << a.get_str() << std::endl;
    std::cout << "String in b: " << b.get_str() << std::endl;
    std::cin.ignore();
}

Вывод:

String in a: How are you?
String in b: How are you?
String in a: I am fine
String in b: I am fine

Почему вы newиспользуете массив символов только для того, чтобы немедленно утечь указатель и перезаписать его?
Эрик

0

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

std::call_once()доступен в C ++ 11; если вы не можете использовать это, это можно сделать с помощью статической логической переменной класса и атомарной операции сравнения и обмена. В своем конструкторе посмотрите, можете ли вы атомарно изменить флаг класса-статики с falseна true, и если это так, вы можете запустить код статической конструкции.

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

В отсутствие C ++ 11 это должно помочь вам начать.

Вот какой-то псевдокод, который поможет вам. Поместите это в определение вашего класса:

enum EStaticConstructor { kNotRun, kRunning, kDone };
static volatile EStaticConstructor sm_eClass = kNotRun;

И это в вашем конструкторе:

while (sm_eClass == kNotRun)
{
    if (atomic_compare_exchange_weak(&sm_eClass, kNotRun, kRunning))
    {
        /* Perform static initialization here. */

        atomic_thread_fence(memory_order_release);
        sm_eClass = kDone;
    }
}
while (sm_eClass != kDone)
    atomic_pause();
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.