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);
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)));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With