4

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

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

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

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

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类中的参数和 中IntPreferenceString参数StringPreference,则无法在基类中声明此函数。的返回值也是如此parseFromString()。我知道这在 C++ 中是不可能的,因为派生类中具有相同名称和不同参数类型的函数只会掩盖而不是覆盖它们的祖先。同样,这就是我用动态语言表达自己的方式,C++ 中的正确模式是什么?

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

Hash(const char *name, Preference pref);
4

7 回答 7

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)));
于 2012-08-10T09:09:36.900 回答
2

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

考虑做类似的事情:

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

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

现在,有很多好的库可以做到这一点。领先的通用 C++ 3rd-party 库是“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...

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

于 2012-08-10T09:33:44.450 回答
1

就我个人而言,我不会为这些东西中的每一个创建单独的分类。它们不可互换,有时您不能提供需要 StringPreference 的 IntPreference ...如果您将抽象“Preference”传递给函数,它将期望它是特定类型以便使用数据.

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

于 2012-08-10T09:06:40.093 回答
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(如果您从字符串中读取)。

于 2012-08-10T09:13:57.550 回答
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;

请注意模板,您必须在适当的位置定义函数。

于 2012-08-10T09:14:02.500 回答
1

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

您的答案在此链接中

于 2012-08-10T09:55:55.970 回答
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;
于 2012-08-10T09:10:44.500 回答