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

Я хочу иметь класс с частным статическим членом данных (вектор, содержащий все символы a-z). В java или C# я могу просто сделать "статический конструктор", который будет работать, прежде чем я сделаю какие-либо экземпляры класса, и настроит статические члены данных класса. Он запускается только один раз (поскольку переменные только читаются и должны быть установлены только один раз), и поскольку это функция класса, она может получить доступ к своим частным членам. Я мог бы добавить код в конструктор, который проверяет, вектор инициализируется и инициализирует его, если это не так, но это вводит много необходимых проверок и не кажется оптимальным решением проблемы.

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

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

21 ответов


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

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

};

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

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

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

Не забывайте (в .СРР) это:

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

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


в рамках .H-файл:

class MyClass {
private:
    static int myValue;
};

в рамках .файл cpp:

#include "myclass.h"

int MyClass::myValue = 0;

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

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

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);
}

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


инициализация статического члена (начиная с C++11)

поскольку C++11, статическая инициализация члена может быть просто реализована с помощью лямбда-функций.

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

class MyClass {
    static std::vector<char> letters;
};

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

std::vector<char> MyClass::letters = [] {
    std::vector<char> letters;
    for (char c = 'a'; c <= 'z'; c++)
        letters.push_back(c);
    return letters;
}();

конструктор статического класса (начиная с C++11)

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

файл:
class MyClass {
    static Foo staticMember1;
    static Foo staticMember2;
    static const bool StaticCtor;    // Note: must be the last static member
};

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

Foo MyClass::staticMember1;
Foo MyClass::staticMember2;

const bool MyClass::StaticCtor = [] {
    staticMember1 = ... ;
    staticMember2 = ... ;
    return true;             // Note: unused dummy return value
}();

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;
}

не нужны


концепция статических конструкторов была введена в 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;

при попытке компиляции и использовать класс Elsewhere (от Earwicker это) Я:

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

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

чтобы сделать эту компиляцию вы можете использовать "статический метод со статической локальной переменной внутри" вместо этого. Что-то вроде этого:--8-->

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

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

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


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

    //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;
    }

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

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

я упростил код и использовать функцию слчис() функция и одно семя initialzer 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();
}

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

на , у вас есть:

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;

на вы можете:

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.


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

#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;
}

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

.файл ГЭС:

vector<char> const & letters();

.файл cpp:

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

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

фу.h

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

фу.cpp

#include "foo.h"

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

int Foo::count = 0;

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

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


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

Как насчет создания шаблона для имитации поведения 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
    };
};

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

#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();
}

это решение?

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

статический конструктор можно эмулировать с помощью класса friend или вложенного класса, как показано ниже.

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

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

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

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

при отсутствии 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();