4

動的言語のバックグラウンドがあるため、C++ などの静的型付け言語で自分の意図を表現するのに問題があることがわかりました。

アプリケーションの設定システムを設計しています。すべての設定にはいくつかの関連する値 (デフォルト値、制限、オブザーバー関数など) があるため、それぞれの設定を独自のオブジェクトにカプセル化することにしました。ここに私の最初のドラフトがあります:

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

IntPreferenceここで、FloatPreferenceやなどの派生クラスをいくつか作成する必要がありますStringPreference。彼らの宣言は次のようになります。

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);
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あなたにとってあまりにも一般的で、あなたの構造を特定の値のセットに制限したい場合は、 use を使用して 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++ サードパーティ ライブラリは「boost」で、引数解析機能を備えています。また、一部の C++ コンパイラには、これの拡張バージョンが付属しています (具体的には、GNU C++ コンパイラには、getopt単に「-f」ではなく「--flag」などのコマンド ライン パラメーターをサポートする「長い」オプション形式が拡張されています)。 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 を与えることはできません...関数に抽象的な「設定」を渡すと、データを利用するために特定の型であることが期待されます。 .

ここではサブクラスをまったく作成しません。別の関数 getIntValue()、getStringValue() などを持つ Preference クラスを作成します。

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

基本クラスにできるだけ多くのロジックを入れようとします。ただし、型ポーリングを避けたい場合は、ここでプロパティ型ごとに 1 つのクラスが必要になると思います。

考え直して、すべてのプロパティ タイプが std::stream からの読み取り/書き込みをサポートしていると仮定するかもしれません。次に、テンプレートを使用して 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) (同じ名前だがパラメーターの型や constness が異なる) を宣言する場合、Base set(int x) は「非表示」になります。 "overloaded" または "overridden" (ベース セット (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 に答える