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


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

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

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

21 157

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

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

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

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

In the .H-файл:

class MyClass {
private:
    static int myValue;
};

In the .файл 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).

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

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

просто решил тот же трюк. Мне пришлось указать определение одного статического члена для Синглтона. Но сделать вещи более сложными-я решил, что я не хочу называть 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();
}

вот мой вариант решения 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). Класс-это лишнее. В этом случае нет необходимости в языковой войне.

.файл HPP:

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 до true, а если так, вы можете запустить код статической конструкции.

для дополнительного кредита сделайте его 3-полосным флагом вместо логического, т. е. не запускайте, не запускайте и не выполняйте запуск. Затем все остальные экземпляры этого класса могут блокироваться до тех пор, пока экземпляр, выполняющий статический конструктор, не завершится (т. е. выдаст ограждение памяти, а затем установит состояние "done running"). Ваш 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();