代码之家  ›  专栏  ›  技术社区  ›  Gordon Gustafson

C++中的静态构造函数?我需要初始化私有静态对象

  •  161
  • Gordon Gustafson  · 技术社区  · 15 年前

    我想要一个具有私有静态数据成员(包含所有字符a-z的向量)的类。在Java或C语言中,我可以做一个“静态构造函数”,它将在生成类的任何实例之前运行,并设置类的静态数据成员。它只运行一次(因为变量是只读的,只需要设置一次),而且由于它是类的函数,所以它可以访问它的私有成员。我可以在构造函数中添加代码,检查向量是否已初始化,如果没有初始化,也可以对其进行初始化,但这会引入许多必要的检查,并且似乎不是解决问题的最佳方案。

    我突然想到,由于变量是只读的,它们可以只是公共静态常量,所以我可以在类外设置它们,但再一次,它看起来有点像一个丑陋的黑客。

    如果我不想在实例构造函数中初始化私有静态数据成员,是否可以在类中拥有它们?

    21 回复  |  直到 6 年前
        1
  •  169
  •   Daniel Earwicker    15 年前

    要获得静态构造函数的等价物,需要编写一个单独的普通类来保存静态数据,然后生成该普通类的静态实例。

    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
  •  73
  •   EFraim    12 年前

    好吧,你可以

    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
  •  19
  •   Ant    15 年前

    在.h文件中:

    class MyClass {
    private:
        static int myValue;
    };
    

    在.cpp文件中:

    #include "myclass.h"
    
    int MyClass::myValue = 0;
    
        4
  •  15
  •   emkey08    6 年前

    C++ 11解决方案

    由于C++ 11,你可以使用 lambda expressions 直接地 初始化静态类成员。你不需要再使用任何解决方法了。

    头文件:

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

    这个图案是 完全替换 对于静态构造函数:

    • 你的定义很明确 初始化顺序 对于所有静态成员:与在 来源 文件。
    • 你可以两者兼而有之 读写 其他(已初始化!)lambda表达式中的静态成员。
    • 可以初始化静态成员, 依赖 其他静态成员。您只需要按照正确的顺序初始化它们。
        5
  •  14
  •   Douglas Mandell    14 年前

    这是另一个类似于丹尼尔·埃尔威克的方法,也使用了康拉德·鲁道夫的朋友级建议。这里我们使用一个内部的私有友元实用程序类来初始化主类的静态成员。例如:

    头文件:

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

    这种方法的优点是完全隐藏初始值设定项类,使类中包含的所有内容都被初始化。

        6
  •  10
  •   CuriousGeorge    11 年前

    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;
    }
    
        7
  •  9
  •   Marc Mutz - mmutz    12 年前

    不需要 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版本。

        8
  •  6
  •   Loki Astari    15 年前

    从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;
    
        9
  •  4
  •   Community CDub    7 年前

    当试图编译和 使用 Elsewhere (从 Earwicker's answer 我得到:

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

    您也可以将参数传递给构造函数或用特定的值初始化它,它非常灵活、强大并且易于实现…唯一的事情是你有一个包含静态变量的静态方法,而不是静态属性…语法有点变化,但仍然有用。希望这对某人有用,

    雨果·冈兹·莱兹·卡斯特罗。

        10
  •  4
  •   Shubham    12 年前

    我想解决这个问题的简单方法是:

        //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
  •  1
  •   adspx5    13 年前

    解决了同样的问题。我必须为singleton指定单个静态成员的定义。 但是让事情变得更复杂-我决定我不想调用randclass()的ctor,除非我要使用它…这就是为什么我不想在代码中全局初始化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
  •  1
  •   Blaisorblade    13 年前

    这是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
  •  1
  •   Jim Hunziker    12 年前

    这是另一个方法,其中向量是通过使用匿名名称空间对包含实现的文件私有的。它对于查找表之类的实现私有的东西很有用:

    #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
  •  1
  •   AndyJost    9 年前

    它当然不需要像目前公认的答案那样复杂(丹尼尔·厄尔威克)。这个班是多余的。在这种情况下,没有必要进行语言战争。

    HPP文件:

    vector<char> const & letters();
    

    CPP文件:

    vector<char> const & letters()
    {
      static vector<char> v = {'a', 'b', 'c', ...};
      return v;
    }
    
        15
  •  0
  •   Nick Lewis    15 年前

    定义静态成员变量的方式与定义成员方法的方式类似。

    福奥

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

    英尺·CPP

    #include "foo.h"
    
    void Foo::bar()
    {
        // method definition
    }
    
    int Foo::count = 0;
    
        16
  •  0
  •   Cristián Romo    15 年前

    要初始化静态变量,只需在源文件内部进行初始化。例如:

    //Foo.h
    class Foo
    {
     private:
      static int hello;
    };
    
    
    //Foo.cpp
    int Foo::hello = 1;
    
        17
  •  0
  •   karmasponge    15 年前

    如何创建一个模板来模仿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
        };
    };
    
        18
  •  0
  •   kriss    14 年前

    对于像这里这样的简单情况,包装在静态成员函数中的静态变量几乎是一样好的。它很简单,通常会被编译器优化掉。但这并不能解决复杂对象的初始化顺序问题。

    #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();
    }
    
        19
  •  0
  •   McDowell rahul gupta    13 年前

    这是解决方案吗?

    class Foo
    {
    public:
        size_t count;
        Foo()
        {
            static size_t count = 0;
            this->count = count += 1;
        }
    };
    
        20
  •  0
  •   Jobin    8 年前

    静态构造函数可以通过以下友元类或嵌套类来模拟。

    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
    
        21
  •  0
  •   ulatekh    7 年前

    哇,我不敢相信没有人提到最明显的答案,而且最接近模仿C静态构造函数行为的答案,也就是说,在创建第一个此类对象之前,它不会被调用。

    std::call_once() 在C++ 11中可用;如果不能使用,则可以使用静态布尔类变量和比较和交换原子操作。在构造函数中,查看是否可以从 false true ,如果是这样,您可以运行静态构造代码。

    对于额外的学分,将其设置为3向标志而不是布尔值,即不运行、运行和完成运行。然后,该类的所有其他实例都可以旋转锁,直到运行静态构造函数的实例完成为止(即,发出内存边界,然后将状态设置为“完成运行”)。您的自旋锁应该执行处理器的“暂停”指令,将每次的等待时间加倍,直到达到一个阈值,等等——这是相当标准的自旋锁技术。

    在没有C++ 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();