2016-04-21 68 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)的。总之,如果你的类初始化不依赖于另一个类的初始化,你可以做后一种方法,因为你不知道哪一个会被初始化。