在c++中定义全局数据的最佳方式是什么?

What it the best way to define global data in c++?

本文关键字:最佳 方式 是什么 数据 c++ 定义 全局      更新时间:2023-10-16

我有一个地图,我想建立(从文件在运行时读取),当应用程序启动,然后由多个类/函数使用。

最好的方法是什么?

Struct GlobalData
{
    static map<int,int> aMap;
    static void buildMap(); //fill in the map
}

然后在main()中调用GlobalData::buildMap(),然后使用映射GlobalData::someMap

或者按如下方式:

map<int,int>& getMap()
{
    static map<int,int> aMap;
    return aMap
}
void buildMap()

然后在main()中调用buildMap(),然后调用getMap()来获取地图

阅读Singleton。根据你的帖子,我认为这可能是一个很好的解决方案。

class GlobalData
{
public:
    static GlobalData* getInstance()
    {
        if (nullptr == sm_Instance) { sm_Instance = new GlobalData(); }
        return sm_Instance;
    }
    map<int, int> getSomeMap() { return m_SomeData; }
private:
    static GlobalData* sm_Instance;
    map<int, int> m_SomeData;
    GlobalData() { buildMap(); }
    void buildMap() { /* build map */ }
};
GlobalData* GlobalData::sm_Instance = nullptr;
int main()
{
    map<int, int> someMap = GlobalData::getInstance()->getSomeMap();
    return 0;
}

直接使用静态构造函数。

Struct GlobalData {
    static map<int,int> aMap;
    static void buildMap() { ... } //fill in the map
    GlobalData() { buildMap(); }
}
GlobalData TheGlobalData;
main() { ... }

为了保证正确的初始化,静态构造函数必须与可能使用它的代码在同一个翻译单元中,例如main()。

您可以定义一个名为ApplicationContext的类。这个类的目的是初始化并保存应用程序所需的"全局"数据。你可以把你从文件中读取的Map放在这个ApplicationContext实例中,并允许你的其他类接受ApplicationContext的实例并使用它的映射。

例如:

// Part of context's construction would be to read the map
ApplicationContext context;
//... After a while
useMap(context);

ApplicationContext类看起来像:

class ApplicationContext {
public:
    ApplicationContext() {
        // Some initial stuff before reading map from file
        loadMapFromFile();
        // Some global stuff to load after
    }
    const std::map<int, int>& getMap() const {
        return aMap;
    }
private:
    void loadMapFromFile() {
        // Code to read your 'global' map from file.
    }
    std::map<int, int> aMap;
};

您可能想要添加更多的参数到您的ApplicationContext类,但您得到了一般的想法。如果每个对象的构造都接受上下文的一个实例,则不必在应用程序中使用单例,并且应用程序的初始化位于ApplicationContext中。

我有一个设计模式,非常适合您的要求,我已经用过好几次了。

请阅读代码

简要视图:有一个GlobalData类,它继承了GlobalDataInfoIface和globaldatappopulateiface。

用户不能直接访问GlobalData。它只能通过两个接口访问全局数据,这取决于它的使用情况。

class GlobalDataPopulateIface {
    public:
        virtual void buildMap() = 0;       
        virtual ~GlobalDataPopulateIface() {}
};
class GlobalDataInfoIface {
    public:
        virtual map<int,int>& getMap() = 0;       
        virtual ~GlobalDataInfoIface() {}
};
class GlobalData : public GlobalDataInfoIface, public GlobalDataPopulateIface
{
    public:
        void buildMap();
        map<int,int>& getMap();
    private:
        // All constructors and destructors are made private
        GlobalData ();   
        ~GlobalData ();   
        GlobalData ( const GlobalData& other );   
        const GlobalData& operator = ( const GlobalData &other ); 
        map<int,int> aMap;
};
class GlobalDataImplInfo {
    public:
        CGSimWaveformImplInfo();
        ~CGSimWaveformImplInfo();
        GlobalDataInfoIface* GetGlobalDataInfoIface();
        GlobalDataPopulateIface* GetGlobalDataPopulateIface();
        static void Destroy();
    private:
        static GlobalData* global_data;
        GlobalDataImplInfo(GlobalDataImplInfo const&);
        GlobalDataImplInfo& operator= (GlobalDataImplInfo const&);
};

想要填充数据的客户端应该有以下代码:

GlobalDataImplInfo global_data;
GlobalDataPopulateIface * global_data_populate = global_data.GetGlobalDataPopulateIface();

想要使用全局数据的客户端应该有以下代码:

GlobalDataImplInfo global_data;
GlobalDataInfoIface * global_data_info = global_data.GetGlobalDataInfoIface();

在这个体系结构中,我们在体系结构本身中定义了全局数据的实现者和用户的边界。因此,有助于引导正确使用全局数据。

全局数据的用户不能修改全局数据。

请让我知道,如果你不明白的代码。

第一个选项中的GlobalData也可以是一个名称空间,因为它的实例是没有意义的。

在实践中,你的选择彼此之间并没有太大的不同。第一个函数在main之前构造映射。第二个函数在第一次调用getMap时构造映射。在任何一种情况下,映射都将在main中填充。如果这样做,那么在静态初始化期间运行的任何代码都将无法使用该映射(在其他转换单元中对静态对象进行反初始化期间也无法使用该映射)。另一个缺点是暴露了对map的非const访问,如果您希望在开始时初始化map,然后再读取它,这可能是不可取的。

可以通过在静态初始化期间填充映射来改善这种情况。Vite Falcons的解决方法是在静态对象的构造函数中调用buildMap(重命名为loadMapFromFile)。这还不够。这是因为map(或者更确切地说,拥有map的context)可能会在依赖它的静态对象之前初始化,也可能不会。

这个答案可以通过使用第一次使用时构造(construct-on-first-use)的习惯用法得到改进,但是如果您更喜欢简单而不是过度封装,这里有一个对第二个选项进行了最小更改的示例,它既允许静态对象使用映射,又允许对映射的引用为const,这是上述封装中最重要的部分。

static map<int,int>* buildMap() {
    auto aMap = new map<int,int>();
    // load the map here
    return aMap;
}
const map<int,int>& getMap() { // use const if you don't need to modify the map
    static map<int,int>* aMap = buildmap();
    return *aMap;
}

如果您不需要在静态(de)初始化期间使用映射,并且您确实希望在初始加载后修改映射,那么您的两个选项都可以。请记住,当某人在某个时刻添加依赖于映射的静态对象(到另一个翻译单元)时,程序可能会中断(甚至更糟:它可能不会!)。