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

193
我想在一个类中拥有一个私有静态数据成员(包含所有小写字母a-z的向量)。在Java或C#中,我可以创建一个“静态构造函数”,它会在创建类的任何实例之前运行,并设置类的静态数据成员。它只会运行一次(因为变量是只读的,且只需要设置一次),而且由于它是类的函数,所以可以访问其私有成员。我可以在构造函数中添加代码来检查向量是否初始化,如果没有则进行初始化,但这引入了许多必要的检查,看起来并不像是解决问题的最佳方案。
我认为既然变量是只读的,它们可以被声明为公共静态常量,这样我就可以在类外部设置它们一次。但再次强调,这似乎仍是一种丑陋的hack方法。
如果我不想在实例构造函数中初始化它们,那么有可能创建一个私有静态数据成员吗?
23个回答

2

哇,我不敢相信没有人提到最明显的答案,也是最接近 C# 静态构造函数行为的答案,即在创建该类型的第一个对象之前不会调用。

std::call_once() 在 C++11 中可用;如果不能使用,可以使用静态布尔类变量和比较交换原子操作来实现。在您的构造函数中,尝试原子地将类静态标志从 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();

1
这是我对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;
}

请注意,仅当存在第[1]行时才会初始化 MyClass :: a ,因为它调用(并需要实例化)构造函数,后者又需要实例化 _initializer 。

1

1

刚刚解决了同样的问题。我不得不为Singleton指定单个静态成员的定义。 但是让事情变得更加复杂-我已经决定,除非我要使用它,否则我不想调用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();
}

1
这里有另一种方法,使用匿名命名空间将向量私有化到包含实现的文件中。这对于像查找表这样的实现私有化的东西非常有用:
#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 命名为更加晦涩的名称,以免在文件较低处意外使用它们。 - Jim Hunziker
1
说实话,很难理解为什么有人会想在实现文件中使用私有静态成员而不是匿名命名空间。 - Jim Hunziker

1
这段文本是关于编程的翻译:

我相信你不需要像目前被接受的答案(由Daniel Earwicker提供)那样复杂。这个类是多余的。在这种情况下,没有必要进行语言之争。

.hpp文件:

vector<char> const & letters();

.cpp文件:

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

0

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

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

0
一个静态构造函数可以通过使用友元类或嵌套类来模拟,如下所示。
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一个char数组,然后立即泄漏指针并覆盖它呢?! - Eric

0

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

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;

2
CrazyJugglerDrummer的问题并不是关于静态普通数据类型的 :) - jww

0

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接