0

我正在开发一个对象和函数库,并有一个名为 的头文件,super.hpp其中包含一些初始化任务。

超级.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 结构时发生一次,因此变量(此处为)是从设置文件中保存的。标头包含在库中的所有对象中,因为不同的对象以不同的容量使用,即无法预测哪些对象将在应用程序中使用,因此我将标头包含在所有对象中以保证它被称为 no使用哪些对象无关紧要。pathsuper.hppsuper.hpp

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

测试.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;
}

A类.hpp

#ifndef H_CLASSA
#define H_CLASSA

class classA{
private:
    double number;

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

#endif

类A.cpp

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

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

B类.hpp

#ifndef H_CLASSB
#define H_CLASSB

class classB{
private:
    int number;

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

#endif

B类.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

但是,当我运行它时,我会得到“设置路径 = foo”。因此,我的结论是 , initializer_struct,init被构造了不止一次。第一次,布尔isInit值为假,设置结构load函数设置path为“bar”。对于所有后续初始化,isInit为真,因此load不会再次调用该函数,并且似乎未初始化settings(即path = "foo")中的变量值覆盖了先前加载的值,因此init.settings.pathin的输出test.cpp

为什么是这样?为什么init每次包含标头时都构造对象?我原以为包含守卫会阻止标题代码被多次调用。如果我inittest.hpp非静态变量中创建变量,则会创建多个副本,并且输出会打印“正在执行初始化”和“正在执行关闭操作”的多次迭代。此外,如果我settings.load()在构造函数中取消注释条件语句之外的函数调用initializer_struct(),则输出会给出“bar”的设置路径。最后,删除super.hppfrom的包含会classA.cpp导致路径值为“bar”,这进一步支持了我的假设,即多个包含test.hpp导致多个构造函数调用。

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

谢谢!

编辑:更新以包括两个对象类以更接近地代表我更复杂的设置

4

3 回答 3

2

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

static bool isInit = false;

static initializer_struct init;

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

initializer_struct(){

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

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

有一种方法可以正确地做到这一点:将static全局对象声明为static类成员,然后static在其中一个翻译单元中实例化这些全局对象。与您合作的翻译单元main()是一个很好的选择。然后,所有内容都会有一个副本。

于 2016-04-21T02:32:35.653 回答
0

根据 Varshavchik 的建议,我做了一些更改。首先,我super.hpp用一个非常基本的类替换了标题,我的库中的所有对象都可以扩展它:

基础.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;
    }
}

其他文件或多或少保持不变,只是有一些变化。首先,两者都classA扩展classBbase

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

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

base::init.settings.path

并且输出现在是我所期望并希望它成为的,Settings path = bar而不是“foo”

于 2016-04-21T14:49:17.263 回答
0

您几乎拥有它,只需将静态 isInit 移动为您的类的静态成员并将 init 的实例化移动到翻译单元。这将是生成的文件:

超级.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

超级.cpp

#include "super.hpp"

bool initializer_struct::isInit = false;
initializer_struct init;

但是,您最好使用单例模式。使用单例模式,您可以确保只实例化您的类的一个实例。你可以在这里获得一些信息:C++ Singleton design pattern

它看起来像这样:

单例.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;
}

单例.cpp

#include "singleton.hpp"

initializer_struct *initializer_struct::_instance = NULL;

您甚至可以通过将 _instance 从指针更改为仅对象,将其声明为 singleton.cpp 中的对象并将 GetInstance() 原型更改为:

initializer_struct &GetInstance() { return _instance; }

但是,对于后者,请注意静态初始化顺序惨败(http://yosefk.com/c++fqa/ctors.html#fqa-10.12)。简而言之,如果您的类初始化不依赖于另一个类初始化,您可以使用后一种方法,因为您不知道哪个会首先被初始化。

于 2016-04-21T15:13:01.113 回答