3

見出しがあまり混乱しないことを願っています。私が持っているのは、StorageManagerから派生したクラスのオブジェクトのリストを含むクラスStorageです。これが例です。

struct Storage {};                         // abstract

class StorageManager
{
private:
    map<string, unique_ptr<Storage>> List; // store all types of storage

public:
    template <typename T>
    void Add(string Name)                  // add new storage with name
    {
        List.insert(make_pair(Name, unique_ptr<Storage>(new T())));
    }

    Storage* Get(string Name)              // get storage by name
    {
        return List[Name].get();
    }
};

SayPositionは特殊なストレージタイプです。

struct Position : public Storage
{
    int X;
    int Y;
};

私の最後の質問に対する素晴らしい答えのおかげで、このAdd関数はすでに機能しています。改善したいのはGet機能です。Storage*次のように使用できるポインタを返すのが妥当です。

int main()
{
    StorageManager Manager;
    Manager.Add<Position>("pos");    // add a new storage of type position

    auto Strge = Manager.Get("pos"); // get pointer to base class storage
    auto Pstn = (Position*)Strge;    // convert pointer to derived class position

    Pstn->X = 5;
    Pstn->Y = 42;
}

派生クラスへのポインタを自動的に返すことで、このポインタキャストを取り除く方法はありますか?たぶんテンプレートを使用していますか?

4

4 に答える 4

2

使用する:

template< class T >
T* Get(std::string const& name)
{
    auto i = List.find(name);
    return i == List.end() ? nullptr : static_cast<T*>(i->second.get());
}

そしてあなたのコードで:

Position* p = Manager.Get<Position>("pos");
于 2012-10-28T15:14:24.433 に答える
2

Get@BigBossがすでに指摘していること以外に、メンバー関数に対して何ができるかわかりませんがAdd、使用済みストレージを返すようにメンバーを改善することはできます。

template <typename T>
T* Add(string Name)                  // add new storage with name
{
   T* t = new T();
   List.insert(make_pair(Name, unique_ptr<Storage>(t)));
   return t;
}

// create the pointer directly in a unique_ptr
template <typename T>
T* Add(string Name)                  // add new storage with name
{
  std::unique_ptr<T> x{new T{}};
  T* t = x.get();
  List.insert(make_pair(Name, std::move(x)));
  return t;
}

編集一時的に私たちがする必要がなくなりdynamic_castます。 EDIT2MatthieuMの提案を実装します。

挿入する型の値をデフォルトの引数で受け入れることで関数をさらに改善することもできますが、追加のコピーが発生する可能性があります。

于 2012-10-28T15:24:41.907 に答える
1

これはひどい考えのように見えるという事実は別として...状況を改善するために私たちが何ができるか見てみましょう。

=>デフォルトの構造を要求するのは悪い考えです

template <typename T>
T& add(std::string const& name, std::unique_ptr<T> element) {
    T& t = *element;
    auto result = map.insert(std::make_pair(name, std::move(element)));
    if (result.second == false) {
        // FIXME: somehow add the name here, for easier diagnosis
        throw std::runtime_error("Duplicate element");
    }
    return t;
}

=>盲目的にダウンキャストするのは悪い考えです

template <typename T>
T* get(std::string const& name) const {
    auto it = map.find(name);
    return it != map.end() ? dynamic_cast<T*>(it->second.get()) : nullptr;
}

しかし率直に言って、このシステムはかなり穴だらけです。そして、そもそもおそらく不要です。一般的な問題を確認して、はるかに優れた設計を考え出すことをお勧めします。

于 2012-10-28T15:47:27.850 に答える
1

あるクラスのオブジェクトへのポインタまたは参照がある場合、それが参照する実際のランタイムオブジェクトは、そのクラスまたは派生クラスのいずれかであることがわかります。 変数を含むコードの一部が2回実行される関数に含まれている可能性があるため、コンパイル時にオブジェクトの実行時型を知るauto ことはできません。auto型システムは、ポリモーフィズムのある言語でどの正確な型が機能しているかを知ることができません。それは、いくつかの制約を提供することしかできません。

オブジェクトの実行時型が(例のように)特定の派生クラスであることがわかっている場合は、キャストを使用できます(使用する必要があります)。static_cast<Position*>(キャストは危険であり、コード内のキャストの検索が容易になるため、フォームのキャストを使用することをお勧めします。)

しかし、一般的に言えば、これを頻繁に行うことは、設計が不十分であることを示しています。基本クラスを宣言し、そこから他のクラスタイプを派生させる目的は、特定のタイプにキャストすることなく、これらすべてのタイプのオブジェクトを同じように処理できるようにすることです。

  • キャストを使用せずにコンパイル時に常に正しい派生型を使用したい場合は、その型の別のコレクションを使用する以外に選択肢はありません。この場合、Positionから派生する意味はおそらくありませんStorage
  • StorageManager::Get()の呼び出し元が行う必要のあるすべてのことを、特定の情報(座標など)をPosition指定しない関数を呼び出すことによって実行できるように物事を再配置できる場合は、これらの関数を、およびで仮想関数にすることができます。それらの特定のバージョンをに実装します。たとえば、オブジェクトをに書き込む関数を作成できます。 とを出力しますが、他の考えられる派生クラスの実装は異なる情報を出力します。PositionStoragePositionPositionStorage::Dump()stdoutPosition::Dump()XYDump()
  • 場合によっては、本質的に無関係ないくつかのタイプの1つである可能性のあるオブジェクトを操作できる必要があります。ここではそうかもしれないと思います。その場合、それboost::variant<>は良い方法です。variantこのライブラリは、Visitorパターンと呼ばれる強力なメカニズムを提供します。これにより、オブジェクトが持つ可能性のあるタイプごとに実行するアクションを指定できます。
于 2012-10-28T16:21:33.457 に答える