Question

I have a series of static classes that I use to populate and access data in a std::map.

With them static, it is easy to set it up so that the map only gets populated if it's ever needed, and only needs to be populated once.

Something along these lines:

class MyClass
{
public:
  string at(int key)
  {
    PopulateEmptyMap();
    return myMap.at(key);
  }

private:
  void PopulateEmptyMap()
  {
    if(!myMap.empty())
      return;
    PopulateMap();
  }

  void PopulateMap()
  {
    myMap[1] = "str1";
    myMap[2] = "str2";
  }

  static map<int, string> myMap;
};

The actual class is more involved, with multiple maps, but you get the idea.

I have a series of classes like this that are virtually identical except for the types and values of the maps.

Instead of having multiple classes all reproducing the same code, I'd like to have the code exist in one place, but I can't figure out how to do so.

My first thought was to inherit from a template class that has all of the shared code. The function PopulateMap would become pure virtual, and then implemented by each inheriting class. This doesn't work, of course, because it's impossible for the function to be both static and virtual.

What are my options for doing what I'm trying?

Was it helpful?

Solution

There is no such thing as a static class. You are describing a class that has only static data that is populated once and used thereafter. You can have as many non-static functions as you want -- both virtual and regular -- as long as they work with just the static member data.

Here's something that seems like what you are looking for.

#include <iostream>
#include <string>
#include <map>

// A base class template that has a virtual function
// to allow derived classes to fill up the map.
template <typename Key, typename Value>
class MyTemplateClass
{
   public:

      Value at(Key const& key)
      {
         PopulateEmptyMap();
         return myMap.at(key);
      }

      void PopulateEmptyMap()
      {
         if(!myMap.empty())
            return;
         PopulateMap();
      }

   protected:

      virtual void PopulateMap() = 0;

      static std::map<Key, Value> myMap;
};

// Define the static member data of the class template.
template <typename Key, typename Value>
std::map<Key, Value> MyTemplateClass<Key, Value>::myMap;

// Define a sub-calss of MyTemplateClass that works
// int as key and string as value.
class MyClass1 : public MyTemplateClass<int, std::string>
{
   protected:

      virtual void PopulateMap()
      {
         myMap[1] = "str1";
         myMap[2] = "str2";
      }
};

// Define a sub-calss of MyTemplateClass that works
// int as key and int as value.
class MyClass2 : public MyTemplateClass<int, int>
{
   protected:

      virtual void PopulateMap()
      {
         myMap[1] = 10;
         myMap[2] = 20;
      }
};

int main()
{
   MyClass1 c1;
   std::cout << c1.at(1) << std::endl;
   std::cout << c1.at(2) << std::endl;

   MyClass2 c2;
   std::cout << c2.at(1) << std::endl;
   std::cout << c2.at(2) << std::endl;
}

Caution:

If you define another derived class that inherits from MyTemplateClass using int as key and int as value, you will see unexpected behavior. There can be only one instantiation of MyTemplateClass<int, int>.

// This will lead to unexpected behavior. You will get either 10 and 20 in
// the map or 30 and 40. You will not get all four in the map.
// If the behavior of MyClass2 seems sane, the behavior of MyClass3 will seem
// the opposite, or vice versa.
class MyClass3 : public MyTemplateClass<int, int>
{
   protected:

      virtual void PopulateMap()
      {
         myMap[1] = 30;
         myMap[2] = 40;
      }
};

However, you can address that problem using a third parameter to MyTemplateClass.

#include <iostream>
#include <string>
#include <map>

template <typename Key, typename Value, typename Derived>
class MyTemplateClass
{
   public:

      Value at(Key const& key)
      {
         PopulateEmptyMap();
         return myMap.at(key);
      }

      void PopulateEmptyMap()
      {
         if(!myMap.empty())
            return;
         PopulateMap();
      }

   protected:

      virtual void PopulateMap() = 0;

      static std::map<Key, Value> myMap;
};


template <typename Key, typename Value, typename Derived>
std::map<Key, Value> MyTemplateClass<Key, Value, Derived>::myMap;

class MyClass1 : public MyTemplateClass<int, std::string, MyClass1>
{
   protected:

      virtual void PopulateMap()
      {
         myMap[1] = "str1";
         myMap[2] = "str2";
      }
};

class MyClass2 : public MyTemplateClass<int, int, MyClass2>
{
   protected:

      virtual void PopulateMap()
      {
         myMap[1] = 10;
         myMap[2] = 20;
      }
};

class MyClass3 : public MyTemplateClass<int, int, MyClass3>
{
   protected:

      virtual void PopulateMap()
      {
         myMap[1] = 30;
         myMap[2] = 40;
      }
};

int main()
{
   MyClass1 c1;
   std::cout << c1.at(1) << std::endl;
   std::cout << c1.at(2) << std::endl;

   MyClass2 c2;
   std::cout << c2.at(1) << std::endl;
   std::cout << c2.at(2) << std::endl;

   MyClass3 c3;
   std::cout << c3.at(1) << std::endl;
   std::cout << c3.at(2) << std::endl;
}

That works for me. Hope it works for you.

Update

There are couple of options that you can choose from that don't require creation of objects on the stack.

All static Members

You can design the classes to make everything accessible through static member functions.

#include <iostream>
#include <string>
#include <map>

template <typename Key, typename Value, typename Derived>
class MyTemplateClass
{
   public:

      static Value at(Key const& key)
      {
         PopulateEmptyMap();
         return myMap.at(key);
      }

      static void PopulateEmptyMap()
      {
         if(!myMap.empty())
            return;
         Derived::PopulateMap();
      }

   protected:

      static std::map<Key, Value> myMap;
};


template <typename Key, typename Value, typename Derived>
std::map<Key, Value> MyTemplateClass<Key, Value, Derived>::myMap;

class MyClass1 : public MyTemplateClass<int, std::string, MyClass1>
{
   public:

      static void PopulateMap()
      {
         myMap[1] = "str1";
         myMap[2] = "str2";
      }
};

int main()
{
   // Access the data without needing to create an object.
   std::cout << MyClass1::at(1) << std::endl;
   std::cout << MyClass1::at(2) << std::endl;

   // They are also accessible using an object.
   MyClass1 c1;
   std::cout << c1.at(1) << std::endl;
   std::cout << c1.at(2) << std::endl;
}

Singleton Pattern

Another approach that might work for you is use of the singleton pattern.

#include <iostream>
#include <string>
#include <map>

template <typename Key, typename Value, typename Derived>
class MyTemplateClass
{
   public:

      Value at(Key const& key)
      {
         PopulateEmptyMap();
         return myMap.at(key);
      }

      void PopulateEmptyMap()
      {
         if(!myMap.empty())
            return;
         PopulateMap();
      }

   protected:

      virtual void PopulateMap() = 0;

      static std::map<Key, Value> myMap;
};


template <typename Key, typename Value, typename Derived>
std::map<Key, Value> MyTemplateClass<Key, Value, Derived>::myMap;

class MyClass1 : public MyTemplateClass<int, std::string, MyClass1>
{
   public:

      static MyClass1* instance()
      {
         static MyClass1 theInstance;
         return &theInstance;
      }

      void PopulateMap()
      {
         myMap[1] = "str1";
         myMap[2] = "str2";
      }

   private:

      // Disallow creation of objects by clients
      MyClass1() {}
      ~MyClass1() {}
};

int main()
{
   // Access the data using the instance() interface.
   std::cout << MyClass1::instance()->at(1) << std::endl;
   std::cout << MyClass1::instance()->at(2) << std::endl;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top