Статические конструкторы в C++? Мне нужно инициализировать частные статические объекты
Я хочу иметь класс с закрытым статическим членом данных (вектор, который содержит все символы az). В Java или C# я могу просто создать "статический конструктор", который будет запускаться до того, как я создам экземпляры класса, и настроить статические члены-данные класса. Он запускается только один раз (поскольку переменные доступны только для чтения и должны быть установлены только один раз), и, поскольку он является функцией класса, он может получить доступ к своим закрытым членам. Я мог бы добавить код в конструктор, который проверяет, инициализирован ли вектор, и инициализировать его, если это не так, но это вводит много необходимых проверок и не кажется оптимальным решением проблемы.
Мне приходит в голову мысль, что, поскольку переменные будут доступны только для чтения, они могут быть просто открытыми статическими константами, поэтому я могу установить их один раз за пределы класса, но, опять же, это выглядит как уродливый хак.
Можно ли иметь частные статические члены-данные в классе, если я не хочу инициализировать их в конструкторе экземпляра?
23 ответа
Чтобы получить эквивалент статического конструктора, вам нужно написать отдельный обычный класс для хранения статических данных, а затем создать статический экземпляр этого обычного класса.
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;
};
Не забудьте (в.cpp) это:
vector<char> MyClass::a;
MyClass::_init MyClass::_initializer;
Программа по-прежнему будет ссылаться без второй строки, но инициализатор не будет выполнен.
Начиная с C++11, вы можете просто использовать лямбда-выражения для инициализации статических членов класса.
Заголовочный файл:
class MyClass {
static vector<char> letters;
};
Файл реализации:
vector<char> MyClass::letters = [] {
vector<char> letters;
for (char c = 'a'; c <= 'z'; c++) { letters.push_back(c); }
return letters;
}();
Примечание. Вы также можете получить доступ к другим статическим членам класса в лямбда-инициализаторе, но только если они были объявлены перед инициализированным членом в заголовочном файле.
В файле.h:
class MyClass {
private:
static int myValue;
};
В файле.cpp:
#include "myclass.h"
int MyClass::myValue = 0;
Вот еще один подход, аналогичный Даниэлю Эрвикеру, также использующий предложение класса от друга Конрада Рудольфа. Здесь мы используем внутренний приватный служебный класс друга для инициализации статических членов вашего основного класса. Например:
Заголовочный файл:
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 от внешнего мира, сохраняя все содержимое этого класса для инициализации.
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;
}
Нет необходимости в 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.
Концепция статических конструкторов была введена в 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;
Я думаю, простое решение этого будет:
//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;
}
При попытке скомпилировать и использовать класс 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;
}
};
И вы также можете передавать аргументы в конструктор или инициализировать его конкретными значениями, это очень гибкий, мощный и простой в реализации... единственное, что у вас есть статический метод, содержащий статическую переменную, а не статический атрибут... синтаксис немного меняется, но все же полезен. Надеюсь, это кому-нибудь пригодится,
Уго Гонсалес Кастро.
Ничего себе, я не могу поверить, что никто не упомянул самый очевидный ответ, и тот, который наиболее близко имитирует поведение статического конструктора C#, то есть он не вызывается до тех пор, пока не будет создан первый объект этого типа.
std::call_once()
доступен в C++11; если вы не можете использовать это, это можно сделать с помощью статической логической переменной класса и атомарной операции сравнения и обмена. В вашем конструкторе посмотрите, можете ли вы атомарно изменить флаг class-static с 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();
Это, конечно, не должно быть так сложно, как принятый в настоящее время ответ (Дэниел Эрвикер). Класс лишний. В этом случае нет необходимости в языковой войне.
Файл.hpp:
vector<char> const & letters();
Файл.cpp:
vector<char> const & letters()
{
static vector<char> v = {'a', 'b', 'c', ...};
return v;
}
GCC предлагает
__attribute__((constructor))
https://gcc.gnu.org/onlinedocs/gcc-4.7.0/gcc/Function-Attributes.html
Пометьте статический метод этим атрибутом, и он будет запускаться при загрузке модуля перед main().
Вот еще один метод, где вектор является закрытым для файла, который содержит реализацию, используя анонимное пространство имен. Это полезно для таких вещей, как таблицы поиска, которые являются частными для реализации:
#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;
}
Просто решил тот же трюк. Мне пришлось указать определение одного статического члена для 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();
}
Вот мой вариант решения 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
,
Как насчет создания шаблона, имитирующего поведение 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
};
};
Вы определяете статические переменные-члены аналогично тому, как вы определяете методы-члены.
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;
Это решение?
class Foo
{
public:
size_t count;
Foo()
{
static size_t count = 0;
this->count = count += 1;
}
};
Чтобы инициализировать статическую переменную, вы просто делаете это внутри исходного файла. Например:
//Foo.h
class Foo
{
private:
static int hello;
};
//Foo.cpp
int Foo::hello = 1;
Для таких простых случаев, как здесь, статическая переменная, заключенная в статическую функцию-член, почти так же хороша. Это просто и обычно оптимизируется компиляторами. Это не решает проблему порядка инициализации для сложных объектов.
#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 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
Я использую эту технику для инициализации статических материалов: В файле .cpp:
struct InitMe {
InitMe(){
// do all the static stuff you want here
}
} initMe;
Поскольку она находится в единице компиляции, переменная initMe будет иметь время жизни программы. Конструктор будет инициализирован до main(), а деструктор, если он есть, будет вызван до завершения программы. Как и все статические вещи, нет гарантии порядка их выполнения, но в одном файле .cpp есть гарантия, что статические вещи будут инициализированы в том порядке, в котором они появляются в файле.