理解多态性设计(C ++)

时间:2012-08-10 08:59:20

标签: c++ polymorphism

根据我在动态语言方面的背景,我发现在使用静态类型语言(如C ++)表达我的意图时遇到了问题。

我正在为我的应用程序设计一个首选项系统。因为每个首选项都会有一些相关的值(默认值,限制,观察者函数......)我决定将每个首选项封装在它自己的对象中。这是我的初稿:

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

现在我需要创建一些派生类,例如IntPreferenceFloatPreferenceStringPreference。以下是他们的声明的样子:

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

既然set()方法在int类中使用IntPreference参数,在String中使用StringPreference参数,则无法声明此函数在基类中。 parseFromString()的返回值也是如此。我理解这在C ++中是不可能的,因为派生类中具有相同名称和不同参数类型的函数只是掩盖,而不是覆盖它们的祖先。再一次,这就是我用动态语言表达自己的方式,C ++中正确的模式是什么?

编辑:抱歉,我忘了提到我需要一个基类来将它们全部存储在哈希表中:

Hash(const char *name, Preference pref);

7 个答案:

答案 0 :(得分:3)

你现在拥有的是一个糟糕的boost::any课程,你可能应该这样做 只是使用它。

您的parseFromString()会员功能可疑。你用的是 动态类型来决定从字符串中解析什么,这是什么 总是必须静态地知道。

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

如果你不喜欢模板,你可以简单地提供一些默认值:

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

编辑:如果boost::any过于通用,您想要限制 你的构造使用了一组特定的值 boost::variant。虽然变体对编译有更大的影响 时间,对于初学者来说可能很难使用。

EDIT2 :哈希表问题:

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)));

答案 1 :(得分:2)

哇 - 慢下来!您有强类型语言。这不是一个设计缺陷,它是故意的:它意味着有点限制,因此它可以对程序的正确性进行编译时检查,并生成更快,更清晰的代码。请不要通过创建一些类型模糊的界面来抛弃类型安全!您的问题中没有任何内容表明您有任何需要这样做。

考虑做以下事情:

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

在解析输入时,您可以填充特定的相关成员变量。

现在,有很多好的库可以做到这一点。领先的通用C ++第三方库是“boost”,它具有参数解析功能。虽然一些C ++编译器附带了增强版本(具体来说,GNU C ++编译器有一个扩展的getopt支持“长”选项格式,用于命令行参数,如“--flag”而不仅仅是“-f”) ,经过尝试和信任的UNIX / Linux工具getopt()可以用作:

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...

无论您是从配置文件,命令行参数,某种注册表方式中读取,概念都是相同的:当您遇到特定的配置值时,请尝试将它们写入特定于其导入的正确类型和命名变量中代码。

答案 2 :(得分:1)

就我个人而言,我不会为每一件事创造单独的分类。它们不是可互换的,有时候你不能给出一个想要StringPreference的IntPreference ......如果你把一个抽象的“Preference”传递给一个函数,它会期望它是一个特定的类型,以便利用这些数据

我不会在这里创建子类,我会有一个Preference类,它有单独的函数getIntValue(),getStringValue()等。

答案 3 :(得分:1)

如果您的偏好设置具有不同类型的值,我认为您不能拥有单一界面。这就是我想到的:

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;
};

我会尝试尽可能多地在基类中加入逻辑。但是如果你想避免类型轮询,我认为你可能需要每个属性类型一个类。

再想一想,您可能会认为所有属性类型都支持读取/写入std :: streams。然后你可以使用模板并使用stringstream(如果你从字符串中读取)。

答案 4 :(得分:1)

您想使用模板:

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;

请注意模板,您必须定义功能。

答案 5 :(得分:1)

如果Base声明了一个成员函数集(int x),并且Derived声明了一个成员函数集(字符串c)(同名但不同的参数类型和/或const),那么Base set(int x)是“隐藏”而不是“重载”或“覆盖”(即使Base set(int x)是虚拟的)

您的Answer is in this link

答案 6 :(得分:0)

你可以使用模板,正如Jacob S.的评论中所建议的那样。它看起来像这样:

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

private:
    T value_;
};

您可以使用它:

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