My mate Aaron and I got talking in the pub a while back about programming
stuff, and I mentioned how I'd done something in Perl recently where Perl's
dynamically scoped variables (created with the keyword local) turned
out to be just right, and solved whatever problem it was really niftily.
Perl's local variables are like global variables, in that they can be
seen by any function, but when they go out of scope they disappear, like
more conventional ‘local’ variables, except that if the local variable
was hiding a global or another local variable of the same name then the
old value gets restored. Hmm, you'd think as a trainer I'd be able to
write a clearer explanation than that, but there you go.
We were saying “of course you can't do anything that cool in C++,
of course”, when we both realized that you could. So we pulled out
Dave's laptop and started hacking. We never quite finished then, because
we drank too much beer to be able to code effectively, but I've just finished
it off. This solution solves one part of the problem: making the elements
in hashes localized. In C++ that translates to associative STL containers,
like map.
The solution (localizer.cc) is to use a class template
Localizer which, when instantiated, stores away the current value of
an element in a container (or remembers that there was no such element, if
that is the case). Instantiating the class is like using local in
Perl and changing the value. When the localizer object goes out of scope
it's destructor is called, which puts the original value back in the
container. Here's the class:
template <typename Cont> class Localizer { public: typedef typename Cont::key_type key_type; typedef typename Cont::data_type data_type; // Remember whether the container originally had a value with the key we // are going to use for the new locally set value, and if so what it was. Localizer (Cont &container, const key_type &key, const data_type &new_value) : container_(container), key_(key), was_here_(container.find(key) != container.end()) { orig_value_ = container[key]; container[key] = new_value; } // If there was originally a value in the container, which has been // temporarily replaced, then put the old value back, otherwise remove // the temporary value. ~Localizer () { if (was_here_) container_[key_] = orig_value_; else container_.erase(container_.find(key_)); } private: Cont &container_; key_type key_; data_type orig_value_; bool was_here_; };
I wrote a little function to print out the contents of maps and the like:
// Print out the contents of an associative container, with dashes to separate // each container's contents. template <typename Cont> void dump_assoc_container (const Cont &container) { static bool first = true; if (first) first = false; else std::cout << std::string(70, '-') << std::endl; for (typename Cont::const_iterator it = container.begin(); it != container.end(); ++it) { std::cout << it->first << " => " << it->second << std::endl; } }
And here's where we use it to temporarily change the value of the element
associated with the word foo in a map (notice the especially
tortuous syntax when creating the localizer variable tmp):
int main () { // Create a map with a value in, and dump it to prove the value is set. std::map<std::string, std::string> things; things["foo"] = "bar"; dump_assoc_container(things); { // Within this scope, set a new value for the key 'foo', and dump it to // show that the value has been changed. Localizer<std::map<std::string, std::string> > tmp (things, "foo", "um"); dump_assoc_container(things); } // Now we're out of that scope, so Localizer's destructor should have put // the map back as it was. dump_assoc_container(things); }
So there you are: instantiation is resource acquisition. Or was it the other way round? Pah.