簡體   English   中英

C++中的靜態構造函數? 我需要初始化私有靜態對象

[英]static constructors in C++? I need to initialize private static objects

我想要一個帶有私有靜態數據成員的類(一個包含所有字符 az 的向量)。 在 java 或 C# 中,我可以創建一個“靜態構造函數”,在創建類的任何實例之前運行它,並設置類的靜態數據成員。 它只運行一次(因為變量是只讀的並且只需要設置一次)並且因為它是類的一個函數,所以它可以訪問它的私有成員。 我可以在構造函數中添加代碼來檢查向量是否已初始化,如果未初始化則對其進行初始化,但這引入了許多必要的檢查,並且似乎不是問題的最佳解決方案。

我突然想到,由於變量將是只讀的,它們可以只是 public static const,所以我可以在類外設置一次,但再一次,這似乎有點像一個丑陋的黑客。

如果我不想在實例構造函數中初始化它們,是否可以在類中擁有私有靜態數據成員?

要獲得靜態構造函數的等效項,您需要編寫一個單獨的普通類來保存靜態數據,然后創建該普通類的靜態實例。

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 解決方案

從 C++11 開始,您可以簡單地使用lambda 表達式來初始化靜態類成員。 您可以在各種靜態成員之間強加一個構造順序,甚至可以將您的靜態成員聲明為const

頭文件:

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. Static members are
// always initialized in the exact same order as defined within the source file.
const size_t MyClass::letterCount = letters.size();

在 .h 文件中:

class MyClass {
private:
    static int myValue;
};

在 .cpp 文件中:

#include "myclass.h"

int MyClass::myValue = 0;

這是類似於 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 類,保持類中包含的所有內容都被初始化。

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 解決方案,但您不能在右側使用字符串文字,因此它並不完全優越。 但是,如果您有除charwchar_tchar16_tchar32_t以外的任何其他類型的向量(其中的數組可以寫為字符串文字),與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類時(來自Earwicker 的回答),我得到:

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 ,如果可以,您可以運行靜態構造代碼。

額外的功勞,使它成為一個 3 路標志而不是一個布爾值,即不運行、運行和完成運行。 然后該類的所有其他實例可以自旋鎖定,直到運行靜態構造函數的實例完成(即發出內存柵欄,然后將狀態設置為“完成運行”)。 你的自旋鎖應該執行處理器的“暫停”指令,每次雙倍等待直到閾值,等等——非常標准的自旋鎖技術。

在沒有 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();

這是另一種方法,其中向量對於包含使用匿名命名空間的實現的文件是私有的。 這對於實現私有的查找表之類的東西很有用:

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

它當然不需要像當前接受的答案(由 Daniel Earwicker 提供)那么復雜。 課是多余的。 在這種情況下不需要語言戰爭。

.hpp 文件:

vector<char> const & letters();

.cpp 文件:

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

海灣合作委員會提供

__attribute__((constructor))

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

用這個屬性標記一個靜態方法,它將在模塊加載時運行,在 main() 之前。

剛剛解決了同樣的技巧。 我必須為 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();
}

這是我的 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

定義靜態成員變量的方式與定義成員方法的方式類似。

foo.h

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

文件

#include "foo.h"

void Foo::bar()
{
    // method definition
}

int Foo::count = 0;

要初始化靜態變量,您只需在源文件中進行。 例如:

//Foo.h
class Foo
{
 private:
  static int hello;
};


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

如何創建一個模板來模仿 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
    };
};

靜態構造函數可以通過使用友元類或嵌套類來模擬,如下所示。

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

對於像這里這樣的簡單情況,包裹在靜態成員函數中的靜態變量幾乎一樣好。 它很簡單,通常會被編譯器優化掉。 但這並不能解決復雜對象的初始化順序問題。

#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 Foo
{
public:
    size_t count;
    Foo()
    {
        static size_t count = 0;
        this->count = count += 1;
    }
};

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM