STL Gotchas – maps
Posted by admin on 20th December 2006
Ah, the STL (C++ Standard Template Library). That viper’s nest of templates within templates, allocators within allocators, unfollowable pointers, and syntax full of arcanity.
Useful, though. Primarily because, increasingly, it comes as part of any competent C++ compiler, and it is reasonably cross-platform compatible (well, the platforms I code on, anyway.)
Maps
Maps can be a particularly useful construct – they store key-value pairs in a structure (typically a balanced b-tree) that facilitates fast ordered insertion and fast lookups.
Here’s an STL map declaration for a bunch of floats, index by strings:
#include <map> #include <string> std::map<std::string, float> floatMap;
Not too terrible (don’t forget the ’std::’!), but everywhere you want to reference/use it, you have to specify the full templated specification. Continuing the above header declarations, for instance:
... void DoSomethingWithMap(std::map<std::string, float> &theMap); // pass a reference to the map in a function
That can get pretty onerous, especially if somewhere down the line you want to change one of the types. So, what I like to do is typedef the map declaration:
#include <map> #include <string> ... typedef std::map<std::string, float> FloatMap; FloatMap floatMap ... void DoSomethingWithMap(FloatMap &theMap);
That’s makes for less typing, and you now have a type associated with the map. Another way you can do this is to subclass it:
#include <mapgt;
#include <string>
...
class FloatMap : public std::map<std::string, float>
{
public:
// override/add functions, here
};
...
// everything else works exactly the same.
//
FloatMap floatMap
...
void DoSomethingWithMap(FloatMap &theMap);
...
Using the Map – the [] Operator
Now, you want to put things in the map, and get things out of the map. There is a very arcane way of doing it, declaring pairs (which is another templated structure) for each of your key-value pairs and calling the ‘insert’ function for each. Fortunately, the implementers provided a simple ‘[]‘ operator, which can be used thusly to assign:
... floatMap["pi"] = atanf(1.0f) * 4.0f; // (3.1415...) ...
And to use it:
... circumf = r * floatMap["pi"] * 2; ...
The Gotcha
All the above is straightforward, but here’s a question: what if you want to test to see if a key is in the map? Intuitively, you might do this:
...
if (!floatMap["avogadro"])
// do something
If “avogadro” is not in the map, the reference will return NULL, which we would expect. But, here’s the “gotcha”: the statement inserts the key “avogadro” with a NULL value. Sure, it will always return null (unless you set it to something), but the key now exists in the map, where the intent was to simply test if the key was there and do something else. If you were testing a whole bunch of different strings, you’d be needlessly filling up the map with keyword pairs.
So, how do we test the value? Well, the formal way goes something like this:
...
using namespace std;
// don't forget this....
FloatMap::iterator fmIter = floatMap.find("avogadro");
if (fmIter != floatMap.end())
value = fmIter->second;
Kinda wordy. We can shorten it to this, remove the iterator declaration, and keep using our intuitive bracket [] operator, as follows:
...
using namespace std;
// don't forget this....
if (floatMap.find("avogadro") != floatMap.end())
value = floatMap["avogadro"]; (// for instance
Ah! That’s neater. And we don’t get the assignment side-effect.
Summary
The STL is useful, and it’s great that compilers are supporting it more uniformly. But it is syntactically arcane and prone to misunderstood behavior, if you’re not careful. Watch especially shortcut techniques and the use of bracket operators.
rickb
Posted in C++, standard template library, stl | No Comments »
