Understanding Design with Polymorphism (C ++)

Based on my background in dynamic languages, I found that I have a problem expressing my intentions in a statically typed language such as C ++.

I am developing a preference system for my application. Since each preference will have several related values โ€‹โ€‹(default value, restrictions, observer function ...), I decided to encapsulate each preference in my own object. Here is my first project:

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

Now I need to create some derived classes, for example IntPreference , FloatPreference and StringPreference . Here's what their ad looks 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 accepts an int parameter in the IntPreference class and a String in StringPreference , there is no way to declare this function in the base class. The same thing happens with the return value of parseFromString() . I understand that this cannot be done in C ++, because functions with the same name and different types of parameters in the derived class simply outshine, rather than override, their ancestors. Again, so I would say myself in a dynamic language, what is the correct C ++ template?

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

 Hash(const char *name, Preference pref); 
+4
source share
7 answers

What you currently have is the poor boost::any class, and you might just need to use this.

The member function parseFromString() doubtful. You use a dynamic type to decide what to parse from a string, something that should always 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 don't like the templates, you can simply provide a few default values:

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

EDIT : If boost::any is too general for you, and you want to restrict your construct to a specific set of values, use boost::variant . Although the option has a greater impact on compilation time, it can be quite difficult to use for beginners.

EDIT2 : 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))); 
+3
source

Woah - slow down there! You have a strongly typed language. This is not a design flaw, it is intentional: it should be a little limited, so it can do compile-time checks on the correctness of your program and produce much faster, cleaner code. Please do not go out of your way to throw away type safety by creating some kind of ambiguous interface! There is nothing in your question to suggest that you need to do this.

Consider doing something like:

 struct Config { int max_for_whatever_; string name_for_whatever_; double scaling_factor_for_whatever_else_; bool verbose_; }; 

When analyzing inputs, you can populate a specific associated member variable.

Now there are many good libraries for this. The leading universal C ++ third-party library is "boost", which has argument analysis tools. And although some C ++ compilers come with extended versions of this (in particular, the GNU C ++ compiler has an extended getopt supporting a "long" format of options for command line options such as "--flag", and not just "-f" ), a proven and reliable UNIX / Linux object getopt() can be used as:

 int c; Config config = { 20, "plugins", 29.3, true }; while ((c = getopt(argc, argv, "m:n:s:v")) != EOF) { switch (c) { case 'm': config.max_for_whatever_ = lexical_cast<int>(optarg); break; case 'n': config.name_for_whatever_ = optarg; break; case 'd': config.scaling_factor_for_whatever_ = lexical_cast<double>(optarg); break; case 'v': config.verbose_ ^= true; break; default: std::cerr << argv[0] << ": unsupported option '" << c << "' - exiting\n"; exit(EXIT_FAILURE); } } // then, use the configuration parameters directly by name... 

The concepts are the same, regardless of whether you read from the configuration file, command line arguments, or in some way the registry: since you are faced with certain configuration values, try writing them to correctly typed and labeled variables specific for importing them into code.

+2
source

Personally, I would not create a separate class for each of these things. They are not interchangeable, you cannot sometimes give the IntPreference that StringPreference wants ... If you pass an abstract "Preference" to a function, it will expect it to be a specific type to use the data.

I would not create subclasses at all, I would have a preference class that has separate functions getIntValue (), getStringValue (), etc.

+1
source

I donโ€™t think you can have one interface if your preferences have different types of values. This is what comes to mind:

 class IPreference { public: virtual ~IPreference() {}; virtual void Parse( std::istream& s ) = 0; virtual void Serialize( std::ostream& s ) = 0; }; template <typename T> class Preference : public IPreference { public: const T& Get() const { return m_value; } void Set(const T& value) const { m_value = value; } private: T m_value; }; 

I would try to put as much logic into the base class as possible. But if you want to avoid polling types, I think you might need one class for each type of property.

On the other hand, you can assume that all of your property types support read / write in std :: streams. Then you can just go with the templates and work with the string stream (if you are reading strings).

+1
source

Do you want to use templates:

 template <typename T> class ChildPreference { T parseFromString(std::string s) { //todo } void set(T newVal) { //todo } // etc. } ChildPreference<int> intObj; ChildPreference<float> fltObj; ChildPreference<std::string> strObj; 

Note that with templates you must define functions in place.

+1
source

if Base declares a set of member functions (int x) and Derived declares a set of member functions (line c) (same name but different types of parameters and / or constant), then the base set (int x) "is hidden "and not" overloaded "or" overridden "(even if the core set (int x) is virtual)

Your answer is in this link

+1
source

You can use templates, as suggested in a comment by Jacob C. It will look something like this:

 template<class T> class Preference { public: parseFromString(std::string s) = 0; T get() { /* some implementation */ } void set(T newValue) { /* some implementation */ } private: T value_; }; 

You would use it like:

 Preference<int> intPrefs; Preference<std::string> stringPrefs; 
0
source

All Articles