2

I have a map that I'd like to build (read from a file at run time) when the application starts and then used by multiple classes/functions.

What is the best way to do it?

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

then call GlobalData::buildMap() in main() and use the map GlobalData::someMap later

Or do it as below:

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

void buildMap()

then call buildMap() in main() and call getMap() to get the map later

Mizipzor
  • 45,705
  • 20
  • 92
  • 136
Ryan
  • 1,773
  • 3
  • 22
  • 33
  • 2
    What does "at startup" mean? Is the map content known at compile time, or does it have to be computed at run time? – Kerrek SB Jul 07 '14 at 22:30
  • It would be read from a file at run time. – Ryan Jul 07 '14 at 22:33
  • It looks like you should review the use of the keyword `extern`. Then perhaps have a header/cpp file combination dedicated to the `GlobalData` struct and/or namespace. – CPlusPlus OOA and D Jul 07 '14 at 22:40
  • 1
    Global data is harmful. Avoid it. – brian beuning Jul 08 '14 at 04:12
  • The correct answer here is that there is no best way, not even any good way, to manage global data in c++ and you should try really, *really* to avoid doing it. If you still want to (but you shouldn't) I'd vote for the singleton pattern below. – Mizipzor Jul 08 '14 at 09:18

5 Answers5

1

Read about Singleton. In accordance with your post, I think it could be fine solution.

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;
}
Mizipzor
  • 45,705
  • 20
  • 92
  • 136
Dakorn
  • 793
  • 5
  • 11
  • Then read about why the Singleton [anti-pattern](http://jalf.dk/blog/2010/03/singletons-solving-problems-you-didnt-know-you-never-had-since-1995/) is [a bad idea](http://stackoverflow.com/questions/137975). Especially in C++, where it's almost impossible to manage the lifetime safely - for example, yours causes both a memory leak and (in a multithreaded program) a data race. – Mike Seymour Jul 08 '14 at 02:55
  • 1
    Doesn't `getSomeMap()` copy the entire map (pass by value)? Should probably be changed to a reference. – Mizipzor Jul 08 '14 at 09:13
1

Simply use a static constructor.

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

main() { ... }

To guarantee correct initialisation, the static constructor must be in the same translation unit as code that may use it, such as main().

david.pfx
  • 9,271
  • 2
  • 19
  • 54
0

You could define a class called ApplicationContext. The purpose of this class would be to initialize and hold 'global' data that are required by your application. You could put your Map that you read from file in this ApplicationContext instance and allow your other class accept an instance of ApplicationContext and use the map from it.

For example:

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

The ApplicationContext class would look like:

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;
};

You might want to add more arguments to your ApplicationContext class, but you get the general idea. If the construction of each object accepts an instance of the context, you don't have to go the path of having a singleton in your application, plus initialization of your application lies within your ApplicationContext.

Vite Falcon
  • 6,375
  • 3
  • 29
  • 48
0

GlobalData in your first option might as well be a namespace since it's instances are pointless.

Your options are not very different from one another in practice. The first one constructs the map before main. The second constructs the map on the first call to getMap. In either case, the map will be populated in main. If you do that, then the map will be unusable to any code that runs during static initialization (it's also unusable during deinitialization of static objects in other translation units). Another drawback is that you expose non-const access to the map, which may not be desirable if you want to initialize the map on start and only read it later.

The situation can be improved by populating the map during the static initialization. Vite Falcons' answer does this by calling buildMap (renamed to loadMapFromFile) in the constructor of a static object. This still isn't quite enough. That's because the map (or rather, context which owns the map) may or may not be initialized before the static objects which depend on it.

That answer can be improved by the use of construct-on-first-use idiom, but if you prefer simplicity over zealous encapsulation, here's an example with minimal changes to your second option which both allows the map to be used by static objects and allows the reference to the map to be const, which is the most important part of the mentioned encapsulation.

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;
}

If you don't need to use the map during the static (de)initialization and you do want to modify the map after the initial load, then either of your options are fine. Just remember that when someone at some point adds a static object (into another translation unit) which depends on the map, then the program might break (or even worse: It might not!).

eerorika
  • 181,943
  • 10
  • 144
  • 256
0

I have a design pattern that very well suites your requirement and I have used it several times.

Please read the code.

Brief view: There is a GlobalData class which inherits from GlobalDataInfoIface and GlobalDataPopulateIface.

User cannot access the GlobalData directly. It can access the global data via two interfaces only, depending upon its usage.

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&);
};

Client who want to populate the data, should have following code:

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

Client who want to use the global data, should have following code:

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

In this architecture we have defined the boundaries of the implementer and user of global data in the architecture itself. Thus, helps in inducing the correct use of Global data.

User of global data cannot modify the global data.

Please let me know if you did not understand the code. We can discuss that.

Abhishek Mittal
  • 356
  • 1
  • 14