Question

It doesn't seem like C++ really has a way to do this, but I'm hoping I'm wrong.

What I'd like to do is create a map (in this case to map one set of strings to another, not that the datatypes should matter really) inside a header file, so that multiple source files can then access that map directly.

Right now I just have a function defined in my header file and implemented in the source file that essentially does this exact function, but I'd prefer to use a map since I would then be able to see at one glance what the keys/values are.

The problem I'm running into with this is that populating the map requires executable code, which I can't exactly just place into a header file. Is there any good way to achieve what I'm trying to do?

Was it helpful?

Solution 3

Here is a solution which does not use C++11. This example also uses three files just to 'prove' it works.

If the map is not intended to be changed, then the initialization should be moved from foo.cpp to foo.h and made const.

foo.h

// FOO.H STARTS HERE
#include <map>
#include <string>
#include <vector>

typedef std::pair<std::string, std::string> stringpair_t;
const stringpair_t map_start_values[] = {
  stringpair_t("Cat", "Feline"),
  stringpair_t("Dog", "Canine"),
  stringpair_t("Fish", "Fish")
};

const int map_start_values_size = sizeof(map_start_values) / sizeof(map_start_values[0]);

extern std::map <std::string, std::string> my_map;

// FOO.H ENDS HERE

foo.cpp

// FOO.CPP STARTS HERE
#include <iostream>

#include "foo.h"

std::map<std::string, std::string> my_map (map_start_values, map_start_values + map_start_values_size);

int main2(void);

int main (void) {
  for (std::map<std::string, std::string>::const_iterator i = my_map.begin(); i != my_map.end(); ++i) {
    std::cout << "A " << i->first << " is a " << i->second << std::endl;
  }

  main2();

  return 0;
}

// FOO.CPP ENDS HERE

foo2.cpp

// FOO2.CPP STARTS HERE
#include "foo.h"

#include <iostream>

int main2 (void) {
  for (std::map<std::string, std::string>::const_iterator i = my_map.begin(); i != my_map.end(); ++i) {
    std::cout << "A " << i->first << " is a " << i->second << std::endl;
  }

  return 0;
}

// FOO2.CPP ENDS HERE

OTHER TIPS

There's a couple of ways to do this.

You can declare the variable in a header file, but you need to define it in a cpp file...

// FOO.H STARTS HERE
#include <map>
#include <string>

extern std::map<std::string, std::string> my_map;
// FOO.H ENDS 

// FOO.CPP STARTS HERE
#include <iostream>

#include "foo.h"

std::map<std::string, std::string> my_map = {
  { "Cat", "Feline" },
  { "Dog", "Canine" },
  { "Fish", "Fish" }
};

int main (void) {
  for (auto i = my_map.begin(); i != my_map.end(); ++i) {
    std::cout << "A " << i->first << " is a " << i->second << std::endl;
  }

  return 0;
}

// FOO.CPP ENDS HERE

Alternatively, you can just keep the map in a header as long as it's const. This has the downside that it obviously only works if you don't need to change the map.

// FOO.H STARTS HERE
#include <map>
#include <string>

const std::map<std::string, std::string> my_map = {
  { "Cat", "Feline" },
  { "Dog", "Canine" },
  { "Fish", "Fish" }
};

// FOO.H ENDS HERE

// FOO.CPP STARTS HERE
#include <iostream>

#include "foo.h"

int main (void) {
  for (auto i = my_map.begin(); i != my_map.end(); ++i) {
    std::cout << "A " << i->first << " is a " << i->second << std::endl;
  }

  return 0;
}

// FOO.CPP ENDS HERE

Both of these examples assume C++11, for the use of the nice and pretty map = { } initializer.

I took this as a challenge -- is there a header-only solution using C++03 syntax?

The answer is "yes":

// GlobalMap.hpp
#include <map>

typedef std::map<std::string, std::string> GMap;

inline
GMap & globalMap()
{
    static GMap theMap;
    static bool firstTime = true;
    if(firstTime)
    {
        firstTime = false;
        theMap["Cat"] = "Feline";
        theMap["Dog"] = "Canine";
        theMap["Guppy"] = "Fish";
    }
    return theMap;
}

To test this:

#include <iostream>
#include "GlobalMap.hpp"
int main()
{
    for(
        GMap::const_iterator it = globalMap().begin();
        it != globalMap().end();
        ++it)
    {
        std::cout << '[' << it->first << "]=" << it->second << std::endl;
    }
   return 0;
}

Produces this output:

[Cat]=Feline
[Dog]=Canine
[Guppy]=Fish

Note I'm not recommending this solution. Just showing it as an interesting possibility.

You can use an initializer list in C++ 11: http://en.cppreference.com/w/cpp/language/list_initialization

But this doesn't solve the problem. You don't want to initialize it in a header file, you will most likely get linker errors (multiple symbol definition).

You can: 1. Use the extern keyword and initialize it in a cpp file as global variable. 2. You can make it a static variable inside a class (but still initialize it in a cpp file).

I had a situation where the map could not be const, but it made way more sense in terms of readability to define the map values in the header. To deal with this situation you can #define your map literal in the header as a macro, and then put the macro in the .cpp file. It's not always ideal to use #defines, but in my case it was worth it for readability.

In the .h file:

#define MY_MAP_LITERAL td::map<std::string, std::string> my_map = {\
  { "Cat", "Feline" },\
  { "Dog", "Canine" },\
  { "Fish", "Fish" }\
};

In the .cpp file:

MY_MAP_LITERAL
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top