static constructors in C++? I need to initialize private static objects
我想要一个具有私有静态数据成员(包含所有字符a-z的向量)的类。在Java或C语言中,我可以做一个"静态构造函数",它将在生成类的任何实例之前运行,并设置类的静态数据成员。它只运行一次(因为变量是只读的,只需要设置一次),而且由于它是类的函数,所以它可以访问它的私有成员。我可以在构造函数中添加代码,检查向量是否初始化,如果没有初始化,也可以对其进行初始化,但这会引入许多必要的检查,并且似乎不是解决问题的最佳方案。
我突然想到,由于变量是只读的,它们只能是公共静态常量,所以我可以在类外设置它们一次,但再一次,它看起来有点像一个丑陋的黑客。
如果我不想在实例构造函数中初始化私有静态数据成员,是否可以在类中拥有它们?
要获得静态构造函数的等价物,需要编写一个单独的普通类来保存静态数据,然后生成该普通类的静态实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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 }; |
好吧,你可以
1 2 3 4 5 6 7 8 9 10 11 | 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中)这一点:
1 2 | vector<char> MyClass::a; MyClass::_init MyClass::_initializer; |
程序仍将在没有第二行的情况下链接,但不会执行初始值设定项。
在.h文件中:
1 2 3 4 | class MyClass { private: static int myValue; }; |
在.cpp文件中:
1 2 3 | #include"myclass.h" int MyClass::myValue = 0; |
C++ 11解决方案
由于C++ 11,您可以简单地使用lambda表达式初始化静态类成员,即使需要在不同静态成员之间强加构造顺序。
头文件:
1 2 3 4 | class MyClass { static vector<char> letters; static size_t letterCount; }; |
源文件:
1 2 3 4 5 6 7 8 9 10 11 12 | 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 use MyClass::letters here. size_t MyClass::letterCount = [] { return letters.size(); }(); |
这是另一个类似于丹尼尔·厄尔威克的方法,也使用了康拉德·鲁道夫的朋友级建议。这里我们使用一个内部的私有友元实用程序类来初始化主类的静态成员。例如:
头文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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; }; |
实施文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // 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); } |
这种方法的优点是完全隐藏初始值设定项类,使类中包含的所有内容都保持初始化状态。
在全局静态初始化期间,只调用一次
调用方只需向要成为其静态构造函数的函数添加一行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 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; } |
不需要
1 2 3 4 5 6 7 8 9 10 | // 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,你可以这样做:
1 2 | // cpp file: std::vector<char> MyClass::alphabet = { 'a', 'b', 'c', ..., 'z' }; |
它在语义上等同于原始答案中的C++ 98解决方案,但是你不能在右手边使用字符串文字,所以它不是完全优越的。但是,如果您有任何其他类型的向量,而不是EDCOX1,2,EDCOX1,3,EDCOX1,4,EDCOX1,5,(它的数组可以被写为字符串文字),C++ 11版本将严格地删除样板代码,而不引入其他语法,与C++ 98版本相比。
从C++中的问题中学习了静态构造函数的概念。所以我们没有直接等价物。
最好的解决方案是使用可以显式初始化的pod类型。或者使静态成员成为具有自己的构造函数的特定类型,该构造函数将正确初始化静态成员。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | //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; |
当试图编译和使用类
1 | error LNK2001: unresolved external symbol"private: static class StaticStuff Elsewhere::staticStuff" (?staticStuff@Elsewhere@@0VStaticStuff@@A) |
如果不将一些代码放在类定义(CPP)之外,似乎不可能初始化非整数类型的静态属性。
要进行编译,可以使用"一个静态方法,其中包含静态局部变量"。像这样:
1 2 3 4 5 6 7 8 9 | class Elsewhere { public: static StaticStuff& GetStaticStuff() { static StaticStuff staticStuff; // constructor runs once, single instance return staticStuff; } }; |
您也可以将参数传递给构造函数或用特定的值初始化它,它非常灵活、强大并且易于实现…唯一的事情是你有一个包含静态变量的静态方法,而不是静态属性…语法有点变化,但仍然有用。希望这对某人有用,
雨果·冈茨·莱兹·卡斯特罗。
我想解决这个问题的简单方法是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | //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; } |
这是另一个方法,其中向量是通过使用匿名名称空间对包含实现的文件私有的。它对于查找表之类的实现私有的东西很有用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #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; } |
海湾合作委员会报价
1 | __attribute__((constructor)) |
https://gcc.gnu.org/onlinedocs/gcc-4.7.0/gcc/function-attributes.html
用这个属性标记一个静态方法,它将在main()之前模块加载时运行。
这是EFRAIM解决方案的变体;区别在于,由于隐式模板实例化,只有在创建类的实例时才调用静态构造函数,并且不需要在
在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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; |
在
1 2 3 4 5 6 7 8 | void foobar() { MyClass foo; // [1] for (vector<char>::iterator it = MyClass::a.begin(); it < MyClass::a.end(); ++it) { cout << *it; } cout << endl; } |
注意,只有当存在行[1]时,
它当然不需要像目前公认的答案那样复杂(丹尼尔·厄尔威克)。这个班是多余的。在这种情况下,没有必要进行语言战争。
HPP文件:
1 | vector<char> const & letters(); |
CPP文件:
1 2 3 4 5 | vector<char> const & letters() { static vector<char> v = {'a', 'b', 'c', ...}; return v; } |
解决了同样的问题。我必须为singleton指定单个静态成员的定义。但是让事情变得更复杂-我决定我不想调用randclass()的ctor,除非我要使用它…这就是为什么我不想在代码中全局初始化singleton。在我的例子中,我还添加了简单的接口。
以下是最终代码:
我简化了代码并使用rand()函数及其单种子初始化器srand()。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | 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 2 3 4 5 6 7 8 9 10 | //Foo.h class Foo { private: static int hello; }; //Foo.cpp int Foo::hello = 1; |
定义静态成员变量的方式与定义成员方法的方式类似。
福奥
1 2 3 4 5 6 7 | class Foo { public: void bar(); private: static int count; }; |
英尺·CPP
1 2 3 4 5 6 7 8 | #include"foo.h" void Foo::bar() { // method definition } int Foo::count = 0; |
如何创建一个模板来模仿C的行为?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | 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 }; }; |
这是解决方案吗?
1 2 3 4 5 6 7 8 9 10 | class Foo { public: size_t count; Foo() { static size_t count = 0; this->count = count += 1; } }; |
静态构造函数可以通过如下所示的友元类或嵌套类来模拟。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 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(); } |
输出:
1 2 3 4 | 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静态构造函数行为的答案,也就是说,在创建第一个此类对象之前,它不会被调用。
EDCOX1(8)在C++ 11中是可用的;如果你不能使用它,它可以用静态布尔类变量和比较和交换原子操作来完成。在构造函数中,查看是否可以自动将类静态标志从
对于额外的学分,将其设置为3向标志而不是布尔值,即不运行、运行和完成运行。然后,该类的所有其他实例都可以旋转锁,直到运行静态构造函数的实例完成(即,发出内存边界,然后将状态设置为"完成运行")。您的自旋锁应该执行处理器的"暂停"指令,每次将等待时间增加一倍,直到达到阈值,等等,这是相当标准的自旋锁技术。
在没有C++ 11的情况下,这应该让你开始。
下面是一些指导您的伪代码。将其放入类定义中:
1 2 | enum EStaticConstructor { kNotRun, kRunning, kDone }; static volatile EStaticConstructor sm_eClass = kNotRun; |
在您的构造函数中:
1 2 3 4 5 6 7 8 9 10 11 12 | 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(); |
对于像这里这样的简单情况,包装在静态成员函数中的静态变量几乎是一样好的。它很简单,通常会被编译器优化掉。但这并不能解决复杂对象的初始化顺序问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | #include <iostream> class MyClass { static const char * const letters(void){ static const char * const var ="abcdefghijklmnopqrstuvwxyz"; return var; } public: void show(){ std::cout << letters() <<" "; } }; int main(){ MyClass c; c.show(); } |