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

Posted

技术标签:

【中文标题】C ++中的静态构造函数?我需要初始化私有静态对象【英文标题】:static constructors in C++? I need to initialize private static objects 【发布时间】:2010-11-14 21:20:48 【问题描述】:

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

我突然想到,由于变量将是只读的,因此它们可以是 public static const,所以我可以在类之外设置它们一次,但再一次,它看起来有点像一个丑陋的 hack。

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

【问题讨论】:

【参考方案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

;

【讨论】:

谢谢!尽管不得不做这一切很烦人。从 C# 和 java 中学到的众多“错误”之一。 是的。我总是向人们指出,如果 C++ 没有犯所有这些“错误”,那么其他语言将不得不犯这些错误。 C++ 涵盖了如此多的领域,甚至会犯错误,这对于它之后的语言来说非常有用。 只有一点细微差别,因为构造函数开始发挥作用,没有人保证静态对象的构造函数何时执行。一个众所周知的更安全的方法是 class Elsewhere StaticStuff& get_staticStuff() static StaticStuff staticStuff; // 构造函数运行一次,当有人第一次需要它时返回 staticStuff; ;我想知道C#和Java中的静态构造函数是否可以提供与上面代码相​​同的保证... @Oleg:是的。在进入 main 之前执行所有非局部变量的构造函数的标准保证。它还保证在一个编译单元内,构造顺序是明确定义的,并且与编译单元内的声明顺序相同。不幸的是,它们没有定义跨多个编译单元的顺序。 这实际上是friend 很有意义的情况,因此Elsewhere 类可以轻松访问StaticStuff 的内部(我可能会补充说,不会以任何危险的方式破坏封装) .【参考方案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;

程序在没有第二行的情况下仍然会链接,但不会执行初始化程序。

【讨论】:

+1(没试过)但是:什么时候调用ctor _init._init()?当我有一个静态 MyClass 对象时,在 MyClass 的 ctor 之前还是之后?我猜你看不出来…… 你好,我在哪里可以找到更多关于这个“初始化”魔法的信息? 不应该是MyClass::a.push_back(i) 而不是a.push_back(i) 吗? @ur.: _initializerMyClass 的子对象。子对象按以下顺序初始化:虚拟基类子对象,按深度优先、从左到右的顺序(但每个不同的子对象仅初始化一次);然后是普通的基类子对象,按深度优先,从左到右的顺序;然后是按声明顺序的成员子对象。所以使用 EFraim 的策略是安全的,只要 _initialiser 中的代码只引用它之前声明的成员。 仅供参考:我添加了所需的静态定义,并将 _init() 设为私有,并测试代码是否仍然有效。【参考方案3】:

C++11 更新

从 C++11 开始,您可以简单地使用 lambda expressions 来初始化静态类成员,即使它们必须以某些特定顺序进行初始化或声明为 const。静态成员的初始化顺序与 source 文件中定义的顺序完全相同。

头文件:

class MyClass 
    static const vector<char> letters;
    static const size_t letterCount;
;

源文件:

// Initialize MyClass::letters with all letters from 'a' to 'z'.
const vector<char> MyClass::letters = [] 
    vector<char> letters;
    for (char c = 'a'; c <= 'z'; c++) letters.push_back(c);
    return letters;
();

// Initialize MyClass::letterCount based on MyClass::letters.
const size_t MyClass::letterCount = letters.size();

【讨论】:

有趣的解决方案。在这种情况下,如果我抛出异常谁能捕捉到它? 静态程序初始化代码必须从不抛出任何异常,否则程序将崩溃。如果可能引发异常,则必须将初始化程序逻辑包装到 try catch 块中。【参考方案4】:

在.h文件中:

class MyClass 
private:
    static int myValue;
;

.cpp 文件中:

#include "myclass.h"

int MyClass::myValue = 0;

【讨论】:

这适用于单个静态成员(无论类型如何)。与静态构造函数相比的不足之处在于您不能在各种静态成员之间强加 order。如果您需要这样做,请参阅 Earwicker 的回答。 我正在这样做,但它仍然无法编译。它说这是问题区域(在构造函数中,而不是标题中)【参考方案5】:

这是另一种类似于 Daniel Earwicker 的方法,也使用了 Konrad Rudolph 的朋友班建议。在这里,我们使用内部私有朋友实用程序类来初始化主类的静态成员。例如:

头文件:

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 类,保留了要初始化的类中包含的所有内容。

【讨论】:

另外,你必须确保ToBeInitialized::Initializer::Initializer()被调用,所以你需要将ToBeInitialized::Initializer ToBeInitialized::initializer;添加到实现文件中。我从你的想法和 EFraim 的想法中得到了一些东西,它完全按照我的需要工作并且看起来很干净。谢谢,伙计。【参考方案6】:

Test::StaticTest() 在全局静态初始化期间只被调用一次。

调用者只需在要成为其静态构造函数的函数中添加一行。

static_constructor&lt;&amp;Test::StaticTest&gt;::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 ) );

但是请注意,类类型的静态变量会在库中引起问题,因此应避免使用它们。

C++11 更新

从 C++11 开始,您可以改为这样做:

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

它在语义上等同于原始答案中的 C++98 解决方案,但是您不能在右侧使用字符串文字,因此它并不完全优越。但是,如果您有除charwchar_tchar16_tchar32_t 之外的任何其他类型的向量(其中的数组可以写为字符串文字),C++11 版本将严格删除样板代码与C++98版本相比,没有引入其他语法。

【讨论】:

我喜欢。虽然如果我们能在没有现在无用的字母表的情况下在一行中做到这一点。 为了引起库的问题,静态类是私有的还是公共的有关系吗?此外,库是静态(.a)还是动态(.so)有关系吗? @ZacharyKraus:什么是公共/私有?不,虽然问题不同,但重叠,库是静态链接还是动态链接都没有关系。 @MarcMutz-mmutz 很抱歉使用了不正确的 C++ 术语的公共/私有类。我指的是上面 EFraim 的解决方案。不过,在我的版本中,我将静态类成员设为私有。我试图了解将静态类成员设为公共或私有是否会对库开发和可用性产生影响。我的直觉告诉我它不应该影响库,因为用户将永远无法访问静态类成员或其构建的对象,但我很想在这个主题上获得一些大师的智慧。 @ZacharyKraus:需要动态初始化([basic.start.init]/2)的静态变量的主要问题是它们运行代码。在库中,可能是在运行析构函数时库代码已经被卸载。如果你想听更多,我建议发布一个关于它的问题。【参考方案8】:

静态构造函数的概念是在 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;

【讨论】:

【参考方案9】:

当尝试编译和使用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;
    
;

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

雨果·冈萨雷斯·卡斯特罗。

【讨论】:

虽然使用线程要小心。我相信在 GCC 中,静态局部变量的构造可以防止并发执行,但在 Visual C++ 中则不然。 从 C++11 开始,在 POSIX 中,它必须是线程安全的。 我非常喜欢上面的另外两个解决方案(this 和this),但是您的解决方案是唯一一个确保按照跨库所需的顺序初始化静态变量的解决方案。我只有一个像你上面那样的私有静态实例方法,并将对其他值的访问包装在使用该实例方法而不是直接引用的公共静态访问器中。谢谢。【参考方案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】:

哇,我不敢相信没有人提到最明显的答案,也是最接近模仿 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();

【讨论】:

【参考方案12】:

刚刚解决了同样的问题。我必须为 Singleton 指定单个静态成员的定义。 但是让事情变得更复杂 - 我已经决定我不想调用 RandClass() 的ctor,除非我要使用它......这就是为什么我不想在我的代码中全局初始化单例。在我的案例中,我还添加了简单的界面。

这是最终代码:

我简化了代码并使用了 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();

【讨论】:

【参考方案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

【讨论】:

【参考方案14】:

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

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

【讨论】:

虽然您可能希望将 Ii 命名为更晦涩的名称,这样您就不会意外地在文件的较低位置使用它们。 说实话,很难理解为什么有人愿意在实现文件中使用私有静态成员而不是匿名命名空间。【参考方案15】:

它当然不需要像当前接受的答案那样复杂(Daniel Earwicker)。课是多余的。在这种情况下,不需要语言战。

.hpp 文件:

vector<char> const & letters();

.cpp 文件:

vector<char> const & letters()

  static vector<char> v = 'a', 'b', 'c', ...;
  return v;

【讨论】:

【参考方案16】:

GCC 提供

__attribute__((constructor))

https://gcc.gnu.org/onlinedocs/gcc-4.7.0/gcc/Function-Attributes.html

使用此属性标记静态方法,它将在模块加载时运行,在 main() 之前。

【讨论】:

【参考方案17】:

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

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;

【讨论】:

CrazyJugglerDrummer 的问题不是关于静态的普通旧数据类型 :)【参考方案18】:

要初始化静态变量,您只需在源文件中执行此操作。例如:

//Foo.h
class Foo

 private:
  static int hello;
;


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

【讨论】:

CrazyJugglerDrummer 的问题不是关于静态的普通旧数据类型 :)【参考方案19】:

如何创建一个模板来模仿 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
    ;
;

【讨论】:

【参考方案20】:

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

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

【讨论】:

【参考方案21】:

这是一个解决方案吗?

class Foo

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

【讨论】:

【参考方案22】:

可以使用如下的友元类或嵌套类来模拟静态构造函数。

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

【讨论】:

你为什么new一个字符数组只是为了立即泄漏指针并覆盖它!?

以上是关于C ++中的静态构造函数?我需要初始化私有静态对象的主要内容,如果未能解决你的问题,请参考以下文章

用反射评估模块的静态构造函数

抽象类中的私有构造函数

使用大量静态方法是一件坏事吗?

第174天:面向对象——公有属性私有属性和静态属性

C++ 中的私有函数与静态函数

私有静态成员函数或匿名命名空间中的自由函数?