在标题

时间:2016-04-21 02:23:51

标签: c++ struct header inclusion

我正在开发一个对象和函数库,并且有一个头文件,这里名为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标头所有这些中都有1}}标题,以保证无论使用哪个对象都会调用它。

我的问题是这样的:当我在主应用程序加载的多个类/对象中包含super.hpp时,结构init似乎被重新初始化并且变量在{构造的{1}}将被默认值覆盖。要查看此操作,请考虑以下附加文件:

TEST.CPP

settings_struct

classA.hpp

#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.cpp

#ifndef H_CLASSA
#define H_CLASSA

class classA{
private:
    double number;

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

#endif

classB.hpp

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

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

classB.cpp

#ifndef H_CLASSB
#define H_CLASSB

class classB{
private:
    int number;

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

#endif

编译并运行示例

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

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

我希望test.out的输出如下:

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

然而,当我运行它时,我得到&#34;设置路径= foo&#34;。因此,我的结论是Doing initialization Settings path = bar Number = 7 Doing closing ops initializer_struct不止一次被构建。第一次,布尔值init为false,设置结构isInit函数将load设置为&#34; bar。&#34;对于所有后续初始化,path为真,因此不会再次调用isInit函数,并且似乎来自未初始化load(即settings)的变量值会覆盖先前加载的值,因此path = "foo"init.settings.path的输出。

这是为什么?为什么每次包含标题时都会构造test.cpp对象?我原以为包含警卫会使标题代码多次被调用。如果我在init中将init变量设为非静态变量,则会创建多个副本,并且输出会打印多次#34;进行初始化&#34;和&#34;做结束操作。&#34;另外,如果我在test.hpp构造函数中的条件语句之外取消注释settings.load()函数调用,那么输出将提供&#34; bar&#34;的设置路径。最后,从initializer_struct()中删除包含super.hpp会导致路径值为&#34; bar&#34;,进一步支持我的假设,即classA.cpp的多个包含会导致多个构造函数调用

我想避免使用test.hpp super.hpp` - 这就是我将命令放在条件语句中的原因。有什么想法吗?如何确保设置文件只读一次并且加载的值不会被覆盖?这是一个完全钝的方法来设置我的库使用的一些标志和设置吗?如果是这样,您是否有任何建议可以使流程更简单和/或更优雅?

谢谢!

编辑:更新为包含两个对象类,以更接近地表示我更复杂的设置

3 个答案:

答案 0 :(得分:2)

在头文件中,您可以定义这些static个全局对象:

static bool isInit = false;

static initializer_struct init;

这些static个全局对象在包含此头文件的每个翻译单元中实例化。您将在每个翻译单元中获得这两个对象的副本。

initializer_struct(){

但是,这个构造函数只会在您的应用程序中定义一次。编译器实际上将在包含这些头文件的每个翻译单元中编译构造函数,并且在每个翻译单元中,构造函数将使用其翻译单元中的static全局对象。

但是,当您链接应用程序时,将删除所有翻译单元中的重复构造函数,并且只有一个构造函数实例将成为最终可执行文件的一部分。未指定哪些重复实例将被删除。链接器将选择其中一个,这是幸运的赢家。无论构造函数的实例是什么,它都只使用来自其自己的翻译单元的static个全局对象。

有一种方法可以正确执行此操作:将static全局对象声明为static类成员,然后在其中一个转换单元中实例化那些static个全局对象。使用main()的翻译单元是一个很好的选择。然后,将会有一个副本。

答案 1 :(得分: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

<强> base.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 {...}

现在,当构造任何对象时,将调用基类构造函数,该构造函数初始化设置和其他变量一次isInitinit都是base类的静态成员,因此包含base标头或扩展base对象的所有对象都可以访问其值,这符合我的需要。可以通过

访问这些值
base::init.settings.path

输出现在是我所期望的,并希望它是Settings path = bar而不是&#34; foo&#34;

答案 2 :(得分: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从指针更改为只是一个对象来进行加载初始化,将其声明为singleton.cpp中的对象并将GetInstance()原型更改为:

initializer_struct &GetInstance() { return _instance; }

然而,后者要注意静态初始化顺序fiasco(http://yosefk.com/c++fqa/ctors.html#fqa-10.12)。简而言之,如果您的类初始化不依赖于另一个类初始化,则可以执行后一种方法,因为您不知道将首先初始化哪一个。