2016-04-21 91 views
0

我正在開發一個對象和函數庫,並且有一個頭文件,這裏的頭文件名爲super.hpp,它包含一些初始化任務。在頭中重複初始化結構

super.hpp

#ifndef H_INIT 
#define H_INIT 

#include <iostream> 
#include <string> 

static bool isInit = false; 

struct settings_struct{ 
    std::string path = "foo"; 
    void load(){ path = "bar"; } 
}; 

struct initializer_struct{ 
    settings_struct settings; 

    initializer_struct(){ 
     if(!isInit){ 
      std::cout << "Doing initialization\n"; 
      settings.load(); 
      isInit = true; 
     } 
     // settings.load(); 
    }//==================== 

    ~initializer_struct(){ 
     if(isInit){ 
      std::cout << "Doing closing ops\n"; 
      isInit = false; 
     } 
    } 
}; 

static initializer_struct init; // static declaration: only create one! 

#endif 

我與此頭意圖是創建initializer_struct對象一次;當它被構造時,這個結構會執行一些操作來設置整個庫的標誌和設置。其中一個操作是創建從XML文件加載設置的設置結構體;當init結構被構造時,這個動作也應該只發生一次,所以變量(這裏是path)從設置文件中保存。 super.hpp標題包含在庫中的所有對象中,因爲不同的對象以不同的容量使用,即無法預測應用程序中將使用哪些對象,因此我在所有對象中都包含super.hpp標題以確保它是不管使用哪個對象都稱爲「

我的問題是這樣的:當我包括super.hpp在多個類/對象都是由主應用程序加載,該結構init似乎被重新初始化並在settings_struct構造有默認被覆蓋的變量設置值。要看到這個動作,可以考慮這些額外的文件:

TEST.CPP

#include "classA.hpp" 
#include "classB.hpp" 
#include <iostream> 

int main(int argc, char *argv[]){ 
    (void) argc; 
    (void) argv; 

    classA a; 
    classB b; 

    std::cout << "Settings path = " << init.settings.path << std::endl; 
    std::cout << "Class A Number = " << a.getNumber() << std::endl; 
    std::cout << "Class B Number = " << b.getInteger() << std::endl; 
} 

classA.hpp

#ifndef H_CLASSA 
#define H_CLASSA 

class classA{ 
private: 
    double number; 

public: 
    classA() : number(7) {} 
    double getNumber(); 
}; 

#endif 

classA.cpp

#include "super.hpp" 
#include "classA.hpp" 

double classA::getNumber(){ return number; } 

classB.hpp

#ifndef H_CLASSB 
#define H_CLASSB 

class classB{ 
private: 
    int number; 

public: 
    classB() : number(3) {} 
    int getInteger(); 
}; 

#endif 

classB.cpp

#include "super.hpp" 
#include "classB.hpp" 

int classB::getInteger(){ return number; } 

要編譯和運行的例子中,

g++ -std=c++11 -W -Wall -Wextra -Weffc++ -pedantic -c classA.cpp -o classA.o 
g++ -std=c++11 -W -Wall -Wextra -Weffc++ -pedantic -c classB.cpp -o classB.o 
g++ -std=c++11 -W -Wall -Wextra -Weffc++ -pedantic classA.o classB.o test.cpp -o test.out 
./test.out 

我期望從test.out輸出爲如下:

Doing initialization 
Settings path = bar 
Number = 7 
Doing closing ops 

但是,當我運行這個,我反而得到「設置路徑=富」。因此,我的結論是initializer_struct,init,不止一次構建。第一次,布爾型isInit爲false,設置結構load函數將path設置爲「bar」。對於所有後續初始化,isInit爲真,因此不會再次調用load函數,並且似乎來自未初始化的settings(即path = "foo")的變量值會覆蓋先前加載的值,因此init.settings.path的輸出將覆蓋test.cpp

這是爲什麼?爲什麼每次包含標題時都會構建init對象?我會認爲包括守衛會保持多次調用標題代碼。如果我在test.hpp中使init變量爲非靜態變量,那麼會創建多個副本,並且輸出會打印多次「執行初始化」和「執行結束操作」的迭代。此外,如果我在initializer_struct()構造函數的條件語句之外取消對settings.load()函數調用的註釋,則輸出會給出「bar」的設置路徑。最後,從classA.cpp中刪除包含super.hpp的結果爲「bar」的路徑值,這進一步支持了我的假設,即多個包含test.hpp會導致多個構造函數調用。

我想避免有settings.load()' called for every object that includes super.hpp` - 這就是爲什麼我將該命令放在條件語句中的原因。有什麼想法嗎?如何確保設置文件只讀一次,並且加載的值不會被覆蓋?這是一個完全鈍的方法來設置我的圖書館使用的一些標誌和設置?如果是這樣,你有任何建議,使過程更簡單和/或更優雅?

謝謝!

編輯:更新以包括兩個對象類,以接近代表我更復雜的設置

回答

0

根據Varshavchik的建議,我做了一些修改。首先,我已經取代了super.hpp頭有一個非常基本的類,它在我的媒體庫中的所有對象可以擴展:

base.hpp

#ifndef H_BASE 
#define H_BASE 

#include <iostream> 
#include <string> 

struct settings_struct{ 
    settings_struct(){ 
     std::cout << "Constructing settings_struct\n"; 
    } 
    std::string path = "foo"; 
    void load(){ path = "bar"; } 
}; 

struct initializer_struct{ 
    settings_struct settings; 

    initializer_struct(){ 
     std::cout << "Constructing initializer_struct\n"; 
    } 

    ~initializer_struct(){ 
     std::cout << "Doing closing ops\n"; 
    } 

    void initialize(){ 
     std::cout << "Doing initialization\n"; 
     settings.load(); 
    } 
}; 

class base{ 
public: 
    static initializer_struct init; 
    static bool isInit; 

    base(); 
}; 

#endif 

基地。cpp

#include "base.hpp" 

initializer_struct base::init; 
bool base::isInit = false; 

base::base(){ 
    if(!isInit){ 
     init.initialize(); 
     isInit = true; 
    } 
} 

其他文件保持大致相同,只是有一些變化。首先,這兩個classAclassB延長base類:

class classA : public base {...} 
class classB : public base {...} 

現在,任何對象的構造的情況下,基類構造函數被調用時,它初始化的設置,其他變量一次isInitinitbase類的靜態成員,因此包含base標頭或擴展base對象的所有對象都可以訪問它們的值,這符合我的需要。這些值是通過

base::init.settings.path 

訪問和輸出是現在我所期望的,並希望它是,與Settings path = bar而不是「富」

2

在你的頭文件中定義這些static全局對象:

static bool isInit = false; 

static initializer_struct init; 

這些static全局對象得到實例化包含此頭文件的每個翻譯單元。您將在每個翻譯單元中擁有這兩個對象的副本。

initializer_struct(){ 

但是,這個構造函數只會在應用程序中定義一次。編譯器實際上將在包含這些頭文件的每個翻譯單元中編譯構造函數,並且在每個翻譯單元中,構造函數將使用其翻譯單元中的全局對象static

但是,當您鏈接您的應用程序時,所有翻譯單元中的重複構造函數將被刪除,並且只有一個構造函數實例將成爲最終可執行文件的一部分。未指定哪些重複的實例將被刪除。鏈接器將選擇其中一個,這是幸運的贏家。無論構造函數的實例是什麼,它都將僅使用來自其自身翻譯單元的全局對象static

有一種方法可以正確地做到這一點:將static全局對象聲明爲static類成員,然後在其中一個翻譯單元中實例化這些全局對象。翻譯單位與您的main()是一個很好的選擇。然後,會有一切的一切。

+0

我有點轉身;我應該將全局對象聲明爲哪個類的靜態成員?如果我讓他們成爲班級成員,他們是不是不再是全球性的? – AndrewCox

+0

不,這是非'靜態'類成員,它是每個類的每個實例的一部分。靜態類成員是全球性的。在C++中查看您最喜歡的書籍,以更深入地討論'static'類成員以及聲明和定義它們的正確方法。 –

+0

啊,對,當然。但是,這仍然會引發一個問題,我會讓他們成爲什麼班級的成員?當前實現的美觀(當然,因爲它不起作用)是我不必在每個'main'函數中實例化一個特定的「初始化」對象。 – AndrewCox

0

你幾乎有它,只需將靜態isInit是一個您的類的靜態成員,並將init的實例化移至翻譯單元。這將是生成的文件:

super.hpp

#ifndef H_INIT 
#define H_INIT 

#include <iostream> 
#include <string> 

struct initializer_struct{ 
    static bool isInit; 

    struct settings_struct{ 
     std::string path = "foo"; 
     void load(){ path = "bar"; } 
    } settings; 

    initializer_struct(){ 
     if(!isInit){ 
      std::cout << "Doing initialization\n"; 
      settings.load(); 
      isInit = true; 
     } 
     // settings.load(); 
    }//==================== 

    ~initializer_struct(){ 
     if(isInit){ 
      std::cout << "Doing closing ops\n"; 
      isInit = false; 
     } 
    } 
}; 

extern initializer_struct init; // extern declaration, instantiate it in super.cpp 

#endif 

super.cpp

#include "super.hpp" 

bool initializer_struct::isInit = false; 
initializer_struct init; 

然而你會使用一個單例模式會更好。通過單例模式,確保只有一個類的實例被實例化。您可以在這裏得到的信息寥寥:C++ Singleton design pattern

它應該是這樣的:

singleton.hpp

#pragma once 

class initializer_struct{ 
public: 
    struct settings_struct{ 
     std::string path = "foo"; 
     void load(){ path = "bar"; } 
    } settings; 

    static initializer_struct *GetInstance() { 
     if (_instance == NULL) { 
      _instance = new initializer_struct(); 
     } 
     return _instance; 
    } 
    ~initializer_struct(){ 
    }  
private: 
    initializer_struct(){ 
     if(!isInit){ 
      std::cout << "Doing initialization\n"; 
      settings.load(); 
      isInit = true; 
     } 
    } 

    static initializer_struct *_instance; 
} 

singleton.cpp

#include "singleton.hpp" 

initializer_struct *initializer_struct::_instance = NULL; 

你可以甚至通過將_instance從指針更改爲j來進行加載初始化烏斯一個對象,它聲明爲在singleton.cpp一個對象,並改變的GetInstance()原型:

initializer_struct &GetInstance() { return _instance; } 

但是與後者提防靜態初始化順序的悲劇(http://yosefk.com/c++fqa/ctors.html#fqa-10.12)的。總之,如果你的類初始化不依賴於另一個類的初始化,你可以做後一種方法,因爲你不知道哪一個會被初始化。