8

C++でキーごとに任意の値型を持つ連想配列を持つ最良の方法は何ですか?

現在、私の計画は、期待するタイプのメンバー変数を持つ「値」クラスを作成することです。例えば:

class Value {

    int iValue;
    Value(int v) { iValue = v; }

    std::string sValue;
    Value(std::string v) { sValue = v; }

    SomeClass *cValue;
    Value(SomeClass *v) { cValue = c; }

};

std::map<std::string, Value> table;

これの欠点は、「値」にアクセスするときにタイプを知る必要があることです。すなわち:

table["something"] = Value(5);
SomeClass *s = table["something"].cValue;  // broken pointer

また、Value に入れる型が多いほど、配列は肥大化します。

より良い提案はありますか?

4

5 に答える 5

14

boost::variantはまさにあなたが探しているもののようです。

于 2008-12-29T11:02:15.593 に答える
10

あなたのアプローチは基本的に正しい方向でした。入れるタイプを知る必要があります何を入れるかを知っている限り、使用するboost::anyことができ、ほぼ何でもマップに入れることができます。

std::map<std::string, boost::any> table;
table["hello"] = 10;
std::cout << boost::any_cast<int>(table["hello"]); // outputs 10

一部の回答では、boost::variantこの問題を解決するために を使用することが推奨されていました。ただし、マップに任意の型付きの値を格納することはできません (必要に応じて)。可能なタイプのセットを事前に知っておく必要があります。それを考えると、上記をより簡単に行うことができます:

typedef boost::variant<int, std::string, void*> variant_type;
std::map<std::string, variant_type> table;
table["hello"] = 10;
// outputs 10. we don't have to know the type last assigned to the variant
// but the variant keeps track of it internally.
std::cout << table["hello"];

その目的のためにboost::variantオーバーロードするため、それは機能します。operator<<バリアントに現在含まれているものを保存したい場合は、次のboost::any場合と同様に、タイプを知る必要があることを理解することが重要です。

typedef boost::variant<int, std::string, void*> variant_type;
std::map<std::string, variant_type> table;
table["hello"] = "bar";
std::string value = boost::get<std::string>(table["hello"]);

バリアントへの割り当ての順序は、コードの制御フローの実行時のプロパティですが、使用される変数の型はコンパイル時に決定されます。したがって、バリアントから値を取得したい場合は、その型を知る必要があります。別の方法は、バリアントのドキュメントで概説されているように、訪問を使用することです。バリアントには、最後に割り当てられたタイプを示すコードが格納されているため、機能します。それに基づいて、使用するビジターのオーバーロードを実行時に決定します。boost::variantは非常に大きく、完全に標準に準拠しているわけではありませんが、boost::any標準に準拠していますが、小さな型でも動的メモリを使用します (したがって、低速です。バリアントは小さな型にスタックを使用できます)。したがって、使用するものをトレードオフする必要があります。

何かを行う方法だけが異なるオブジェクトを実際にそこに入れたい場合は、ポリモーフィズムがより良い方法です。派生元の基本クラスを持つことができます:

std::map< std::string, boost::shared_ptr<Base> > table;
table["hello"] = boost::shared_ptr<Base>(new Apple(...));
table["hello"]->print();

基本的にこのクラスレイアウトが必要になります:

class Base {
public:
    virtual ~Base() { }
    // derived classes implement this:
    virtual void print() = 0;
};

class Apple : public Base {
public:
    virtual void print() {
        // print us out.
    }
};

boost::shared_ptrいわゆるスマートポインターです。オブジェクトをマップから削除すると、オブジェクトが自動的に削除され、他に何も参照されなくなります。理論的には、プレーン ポインターでも作業できますが、スマート ポインターを使用すると安全性が大幅に向上します。リンク先のshared_ptrマニュアルを読んでください。

于 2008-12-29T10:58:30.813 に答える
2

、などでサブクラス化Valueします。IntValueStringValue

于 2008-12-29T10:56:04.623 に答える
2

std::map で共用体を使用できますか?

Boost::variant は型のない変数を提供します。

または、すべての値データ メンバーを非公開にして、設定されていない場合にエラーを返す (またはスローする) アクセサーを提供することもできます。

于 2008-12-29T11:00:49.637 に答える
1

unionキーとして常に値の 1 つしか持たないため、簡単な最適化は a を使用することです。

より完全なソリューションは、いくつかの実行時の型情報をインターフェイスにカプセル化することです。主に「これはどのタイプ?」および「値が等しいかどうかを比較するにはどうすればよいですか?」次に、その実装をキーとして使用します。

于 2008-12-29T11:01:13.880 に答える