I would define a base class Layer
that has a pointer to a parent layer and a name that you can print. Then you can define all the layers in your hierarchy by simply inheriting from this Layer
and using the new C++11 inheriting constructor feature to avoid code repetition (in C++98 you would have to define the constructor in every concrete layer).
I would not store the children inside the layer but in a std::multimap<Layer*, Layer*>
instead, so that you can use the convenient member function equal_range
to get all children of any concrete layer inside your application (I called this data structure Atlas
).
#include <map>
#include <string>
#include <utility>
#include <vector>
#include <iostream>
class Layer
{
public:
explicit Layer(std::string n, Layer* p = nullptr): name_(n), parent_(p) {}
std::string name() { return name_; }
Layer* parent() { return parent_; }
private:
std::string name_;
Layer* parent_;
};
// inheriting constructrors (C++11 only, retype constructors for C++98)
class City : public Layer { using Layer::Layer; };
class Country: public Layer { using Layer::Layer; };
class Planet : public Layer { using Layer::Layer; };
typedef std::multimap<Layer*, Layer*> Atlas;
int main()
{
Planet Earth("Earth");
Country USA("United States of America", &Earth);
Country FRA("France", &Earth);
std::vector<City> USA_cities = { City("Los Angeles", &USA), City("New York", &USA) };
std::vector<City> FRA_cities = { City("Bordeaux", &FRA), City("Paris", &FRA) };
Atlas a;
a.insert(std::make_pair(&Earth, &USA));
a.insert(std::make_pair(&Earth, &FRA));
for (auto& city: USA_cities)
a.insert(std::make_pair(&USA, &city));
for (auto& city: FRA_cities)
a.insert(std::make_pair(&FRA, &city));
auto countries = a.equal_range(&Earth);
std::cout << "Countries on planet " << countries.first->first->name() << " :\n";
for (auto it = countries.first; it != countries.second; ++it) {
auto country = it->second;
std::cout << country->name() << " (located on planet " << country->parent()->name() << "), with cities:\n";
auto cities = a.equal_range(country);
for (auto jt = cities.first; jt != cities.second; ++jt) {
auto city = jt->second;
std::cout << " " << city->name() << " (located in country " << city->parent()->name() << ")\n";
}
std::cout << "\n";
}
}
Live Example with the following output:
Countries on planet Earth :
United States of America (located on planet Earth), with cities:
Los Angeles (located in country United States of America)
New York (located in country United States of America)
France (located on planet Earth), with cities:
Bordeaux (located in country France)
Paris (located in country France)
As you can see, you can iterate up and down this hierarchy.
You can critique this solution on several points. There is no enforcement on the class hierarchy: a Planet
can have a list of Cities
without intermediate Country
layer, etc. You could enforce such things through either compile-time checking or through runtime type information. This is a tradeoff between flexibility and safety.
There is also no virtual
destructor in the base class Layer
. As long as you don't try to delete any concrete layer objects through Layer*
, there is no problem with that. There are also no smart pointers because all the Layer*
are non-owning pointers. If you ever want to move or copy buildings around (it happens sometimes), you could extend it easily.