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

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

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

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

Ответ 1

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

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

};

Ответ 2

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

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;

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

Ответ 3

В файле .h:

class MyClass {
private:
    static int myValue;
};

В файле .cpp:

#include "myclass.h"

int MyClass::myValue = 0;

Ответ 4

С++ 11 решение

Начиная с С++ 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

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

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

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 от внешнего мира, сохраняя все, что содержится в классе, для инициализации.

Ответ 6

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

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

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

Ответ 7

Нет необходимости в функции 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 ) );

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

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

Как и в С++ 11, вы можете сделать это вместо:

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

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

Ответ 8

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

Лучшим решением является использование типов 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;

Ответ 9

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

error LNK2001: unresolved external symbol "private: static class StaticStuff Elsewhere::staticStuff" ([email protected]@@[email protected]@A)

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

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

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

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

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

Ответ 10

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

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

Ответ 11

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

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

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

Ответ 12

Здесь мой вариант решения 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.

Ответ 13

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

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

Ответ 14

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

.hpp файл:

vector<char> const & letters();

.cpp файл:

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

Ответ 16

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

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;

Ответ 17

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

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


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

Ответ 18

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

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

Ответ 19

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

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

Ответ 20

Это решение?

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

Ответ 21

Статический конструктор можно эмулировать с помощью класса 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

Ответ 22

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

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

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

В отсутствие С++ 11, this должен начать работу.

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

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