Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding design with polymorphism (C++)

Coming from my background in dynamic languages, I find I have a problem expressing my intentions in a statically-typed language such as C++.

I'm designing a preference system for my application. As every preference will have a few associated values (the default value, limits, an observer function...) I decided to encapsulate each preference in an object of its own. Here's my first draft:

class Preference    // purely abstract class
{
    parseFromString(String s) = 0;
    get() = 0;
    void set(newVal) = 0;
private:
    // internal data
};

Now I need to create a few derived classes, like IntPreference, FloatPreference and StringPreference. Here's how their declaration would look like:

class IntPreference : Preference          class StringPreference : Preference
{                                         {
    int parseFromString(String s);            String parseFromString(String s);
    void set(int newVal);                     void set(String newVal);
    // etc.                                   // etc.
}                                         }

Now that the set() method takes an int parameter in the class IntPreference and a String parameter in StringPreference, there's no way to declare this function in the base class. The same goes with the return value of parseFromString(). I understand this is impossible to do in C++, because functions with the same name and different parameter types in a derived class just overshadow, not override their ancestors. Again, this is how I would express myself in a dynamic language, what's the correct pattern in C++?

EDIT: Sorry, I forgot to mention I need a base class to store them all in a hash table:

Hash(const char *name, Preference pref);
like image 364
peter.slizik Avatar asked Jan 15 '23 20:01

peter.slizik


1 Answers

What you have now, is a poor boost::any class and you maybe should simply just use that.

Your parseFromString() member function is dubious. You use the dynamic type to decide what to parse out of the string, something that always has to be known statically.

class my_any {
public:
  template<typename T>
  explicit // don't rely on conversions too much
  my_any(const T& t) : x_(t) {}

  // might throw if the cast fails
  template<typename T>
  T& get() { return boost::any_cast<T&>(x_); }

  // also maybe move semantics
  template<typename T>
  set(const T& t) { x_ = t; }
private:
  boost::any x_;
};

// usage:
my_any m;
m.set(23);
try {
  int& x = m.get<int>();
catch(boost::bad_any_cast& ex) {
  // ...
}

// for setting things from string just do 
// the right thing at the call site of set

If you dislike templates you can simply provide a few defaults:

my_any::getInt(); my_any::getString();

EDIT: If boost::any is too generic for you and you want to limit your construct to a certain set of values use boost::variant. Although a variant has a larger impact on compile time and can be quite hard to use for a beginner.

EDIT2: The hash table problem:

typedef boost::unordered_map<std::string, my_any> preference_table;
preference_table t;
// i added a template constructor to my_any
t.insert(std::make_pair("Foobar", my_any(23)));
like image 129
pmr Avatar answered Jan 25 '23 05:01

pmr