C++仿制靜態構造函數


在《C++的頭文件和實現文件分別寫什么》文章中,我對於的C++的數據成員,逐個分析了可以作用在它們上邊的限定符都有哪些,以及它們所對應的進行初始化的位置。可以看出這些修飾符其實就是const和static的兩種的組合,但是卻有不同的效用。

本文,我想講關於static的問題,《C++的頭文件和實現文件分別寫什么》已經指出如果數據成員被聲明為static,那么它在編譯時就必須被初始化。僅含static的則放在類之外,實現文件之中;同時含有的const的則放在類之內,直接跟在數據的定義之后。

在我實際代碼編寫中碰到的問題是:static成員的初始化比較的復雜,步驟較多,需要調用另一個函數來完成。此時,簡單使用賦值語句就不太能完成那些目的了。

這個問題來源於我在OpenGL中想使用gluCylinder()函數,該函數需要傳入一個指向GLUquadric 對象的指針。其初始化的過程如下:

GLUquadric * quad = gluNewQuadric();
gluQuadricDrawStyle(quad, GLU_FILL);
gluQuadricNormals(quad, GLU_SMOOTH);

我將我要畫的圖抽象成了一個類,將gluCylinder()的畫圖過程封裝在了一個函數之中。可以看出quad實際上只是用來指示所畫的圖形的樣式和表面向量的計算方式。因此,我覺得完全可以定義為static讓所有類對象共享。

於是我就碰到了static初始化難的問題。同樣的問題還可能發生在容器(Container)類型中,比如我們想要對static的容器類型裝入一些初始的值。


如果是C#,它提供了靜態構造函數(static constructor)。在MSDN上,對於靜態構造函數的表述如下:

    靜態構造函數既沒有訪問修飾符,也沒有參數。

    在創建第一個實例或引用任何靜態成員之前,將自動調用靜態構造函數來初始化類。

  • 靜態構造函數既沒有訪問修飾符,也沒有參數。
  • 在創建第一個實例或引用任何靜態成員之前,將自動調用靜態構造函數來初始化類。
  • 無法直接調用靜態構造函數。
  • 在程序中,用戶無法控制何時執行靜態構造函數。
  • 靜態構造函數的典型用途是:當類使用日志文件時,將使用這種構造函數向日志文件中寫入項。
  • 靜態構造函數在為非托管代碼創建包裝類時也很有用,此時該構造函數可以調用 LoadLibrary 方法。
  • 如果靜態構造函數引發異常,運行時將不會再次調用該構造函數,並且在程序運行所在的應用程序域的生存期內,類型將保持未初始化。

 

可惜C++沒有提供靜態構造函數的構造方式,只能另辟蹊徑來尋求解決方案:

  1. 提供一個靜態(非靜態亦可)輔助函數來完成初始化操作;
  2. 放棄static修飾符,讓每個類都有自己的一份。

對於方案1,我們就需要在使用靜態成員之前,在代碼中顯示地調用一次初始化函數。這就沒有C#靜態構造函數“自動”、“隱式”的優點了。但是一旦忘記調用初始化方程,我們就會得到錯誤的數據。而且遺忘的可能性又是如此之大。

當然我們可以限定成Get方法,在調用數據的地方(無論是類外部,還是類其他函數的調用)都使用Get方法,這樣我們可以一定程度上做到“自動”和“隱式”。

當然我們需要防止成員被初始化兩次以上。對於指針類型,我們可以用判斷是否為空指針來辨別是否已經初始化了(這就相當於是個Singleton Pattern);對於容器類型,我們可以判定容器內成員的數目來辨別(如果我們的初始話數目是一定的)。但是如果是一個普通的對象,似乎就需要多一個標記來進行指示了。

class Picture
{
public:
    virtual ~Picture(){}
GLUquadric
* GetQudric() { //General way to avoid twice initialization. static bool inited = false; if (!inited) { quad = gluNewQuadric(); gluQuadricDrawStyle(quad, GLU_FILL); gluQuadricNormals(quad, GLU_SMOOTH);
       inited = true; }
return quad; } protected: static GLUquadric* quad; }; GLUquadric* Picture::quad;

 

對於方案2,如果是像我在OpenGL里面需要的成員只是作為輔助,顯然沒有問題。可是假如我們就是希望能夠對數據進行共享的話,自然就失去了意義。

 

其實很多問題,前人都已經做了優美的解決方法,主動學習要好於閉門造車。所以Google一下,在stackoverflow高手們就給了一個更加接近於靜態構造函數的方法:

To get the equivalent of a static constructor, you need to write a separate ordinary class to hold the static data and then make a static instance of that ordinary class.
(將需要使用static的數據用一個普通類來進行封裝, 在該類的構造函數中進行所需的初始化步驟。然后在原來的類中定義一個該類的靜態對象。)

以我所需的GLUquadric為例,我構建了一個新的Quadric類,該類具有GLUquadric指針成員,並且提供了一個對外的接口。然后,我在要使用的GLUquadric的靜態指針對象類里,改用Quadric靜態對象。這樣我們就能做到了隱式地自動地進行初始化了。

 

//Header File

#ifndef QUADRIC_H
#define QUADRIC_H

class Quadric
{
public:
    Quadric()
    {
        quad = gluNewQuadric();
        gluQuadricDrawStyle(quad, GLU_FILL); 
        gluQuadricNormals(quad, GLU_SMOOTH);
    }
    ~ Quadric(){ if(quad)gluDeleteQuadric(quad); }
    
    GLUquadric * Object(){return quad;}
    
private:
    GLUquadric * quad;
};

class Picture
{
public:
    virtual ~Picture(){}
    void DrawTrunk();
protected:
    static Quadric quadric;    
};

#endif
//Implementation File
//Definition static member data Quadric Picture::quadric; void Picture::DrawTrunk() { glPushMatrix(); glRotated(-90, 1, 0, 0); glColor3f(0.625, 0.14, 0.14); glScalef(7, 7, 21); gluCylinder(quadric.Object(), 1, 1, 1, 20, 20); glPopMatrix();
}

 

如果所用靜態數據是對象而非指針的話,對外的接口返回類型可以從指針換成引用類型

另外,因為我們有類將其包裹,所以我們可以把數據的銷毀過程也封裝在類的析構函數之中。不過因為對象是以static形式被使用的,所以從程序開始被創建,直到程序結束被自動銷毀。所以大部分時候,其實不需要去考慮析構的問題。

唯一的不足,可能就是原本是直接調用我們需要的對象或直接,現在則需要通過新類的接口訪問。

不過我們也可以重載一些轉換運算符來一定程度的規避這個問題:

class Quadric
{
public:
    Quadric()
    {
        quad = gluNewQuadric();
        gluQuadricDrawStyle(quad, GLU_FILL); 
        gluQuadricNormals(quad, GLU_SMOOTH);
    }
    ~ Quadric(){ if(quad)gluDeleteQuadric(quad); }
    
    GLUquadric * Object(){return quad;}
    
    //operators overloading
    operator GLUquadric *(){ return quad; }
    operator GLUquadric &(){ return* quad; }
    GLUquadric& operator *(){ return* quad; }
    
private:
    GLUquadric * quad;
};

 



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM