【问题标题】:C++ - Map of Vectors of Smart Pointers - All inherited from the same base classC++ - 智能指针向量映射 - 全部继承自同一个基类
【发布时间】:2016-09-03 03:08:02
【问题描述】:

我的实体组件系统中有这张地图:

std::map<u_int32_t, std::vector<std::shared_ptr<Component>>> _componentMap;

u_int32_t 是组件向量的关键。同一个组件可以有多个实例。 (这就是为什么有一个向量)。

现在我想要一个模板化的 getter 函数,它返回一个继承类型的 Vector:

template<class T> inline const std::vector<std::shared_ptr<T>> & getVector() const
{
     u_int32_t key = getKey<T>();
     return static_cast<std::vector<std::shared_ptr<T>>>(_componentMap.count(key) ? _componentMap.at(key) : _emptyComponentVec);
}

我知道这不起作用,因为不同类型的std::vectors 完全不相关,我无法在它们之间进行转换。我还想避免每次调用这个函数时分配一个新的向量。

但是我怎样才能得到想要的行为呢?添加组件后,我可以创建所需派生类型的std::vector

问题也可能是:我怎样才能拥有一个包含不同类型std::vectorstd::map

对于我无法链接到 boost 的任何解决方案,但如果绝对需要,我可以集成 boost 的单个标头。

【问题讨论】:

  • 想知道std::tuple&lt;std::vector&lt;std::shared_ptr&lt;TComponents&gt;&gt;...&gt; 是否更合适。
  • 抱歉,您能再解释一下吗?我不是 C++ 专家。我用过元组,但我不知道他们会如何解决它。如果可行,我也愿意更改我的数据结构。
  • 我的意思是,类似于that
  • 啊,我明白了,但这需要在初始化时命名所有可能的组件......这也不是很理想。
  • 如果你可以使用一些范围库,你可以做一个*视图*或你的组件向量而不分配。

标签: c++ c++11 dictionary vector stl


【解决方案1】:
template<class It>
struct range_view {
  It b, e;
  It begin() const { return b; }
  It end() const { return e; }
  using reference = decltype(*std::declval<It const&>());
  reference operator[](std::size_t n) const
  {
    return b[n];
  }
  bool empty() const { return begin()==end(); }
  std::size_t size() const { return end()-begin(); }
  reference front() const {
    return *begin();
  }
  reference back() const {
    return *std::prev(end());
  }
  template<class O>
  range_view( O&& o ):
    b(std::begin(o)), e(std::end(o))
  {}
};

这是一个快速范围视图。可以改进。

现在您需要做的就是编写一个伪随机访问迭代器来转换其参数。因此,它对类型 T 进行随机访问迭代器,然后执行一些操作 F 以返回类型 U。它转发所有其他操作。

map 然后存储std::vector&lt;std::shared_ptr&lt;Base&gt;&gt;。 gettor 返回一个range_view&lt; converting_iterator&lt;spBase2spDerived&gt; &gt;

【讨论】:

  • 谢谢,这看起来很有趣,但现在有点超出我的想象。我仍然不明白范围视图到底是什么(尽管阅读了一些解释)
  • 范围视图就像一个指针,但它不是指向一个元素,而是指向一个范围。它不支持添加或删除容器中的元素,只是查看它们。
  • 好的,我明白了。不幸的是,我仍然不知道如何使用它,我不知道如何编写伪随机访问迭代器。我还想避免每次调用 getVector 时都必须分配 range_view 。开销可能很小,但我觉得必须有一个更简单的解决方案。
【解决方案2】:

这是我为这个问题想到的解决方案的粗略实现。当然,还有很多地方可以完善代码,但希望它能传达我的想法。

#include <iostream>
#include <map>
#include <vector>
#include <memory>
using namespace std;

class Base {
public:
    virtual void f() const = 0;
};

class A : public Base {
public:
    static const int type = 0;
    explicit A(int a) : a_(a) {}
    void f() const { cout << "calling A::f" << endl;}
    int a_;
};
class B : public Base {
public:
    static const int type = 1;
    explicit B(int a) : a_(a) {}
    void f() const { cout << "calling B::f" << endl;}
    int a_;
};

class MapWrapper {
public:
    template<class T>
    void append(int a, vector<T> const& vec) {
        types_[a] = T::type;
        my_map_[a] = make_shared<vector<T>>(vec);
    }
    template<class T>
    vector<T> const& get(int a) const {
        return *static_pointer_cast<vector<T>>( my_map_.at(a) );
    }
    map<int, shared_ptr<void>> const& get_my_map() const {
        return my_map_;
    }
    vector<shared_ptr<Base>> get_base(int a) const {
        vector<shared_ptr<Base>> ret;
        switch(types_.at(a)) {
            case 0: {
                auto const vec = get<A>(a);
                for(auto v : vec)
                    ret.push_back(make_shared<A>(v));
                break;  
            }
            case 1: {
                auto const vec = get<B>(a);
                for(auto v : vec)
                    ret.push_back(make_shared<B>(v));
                break;  
            }
        }
        return ret;
    }
    map<int, shared_ptr<void>> my_map_;
    map<int, int> types_;
};  

int main() {
    MapWrapper map_wrapper;
    map_wrapper.append(10, vector<A>{A(2), A(4)});
    map_wrapper.append(20, vector<B>{B(5), B(7), B(9)});

    for(auto const& w : map_wrapper.get_my_map())
        for(auto v : map_wrapper.get_base(w.first))
            v->f();

    for(auto const& x: map_wrapper.get<A>(10))
        cout << x.a_ << " ";
    cout << endl;

    for(auto const& x: map_wrapper.get<B>(20))
        cout << x.a_ << " ";

    return 0;
}

【讨论】:

  • 如果我没看错的话,你将 void shared_pointers 存储到映射中不同类型的 std::vector 中,然后当被要求将它们转换回各自的类型时?
  • 我今天尝试实现你的解决方案,我发现了一个我以前没有看到的主要问题:如果地图只有一个shared_ptr void,我不能再使用函数迭代所有组件基类。例如,当实体进入场景时,我在其所有组件上调用 onEnter。这是否也适用于您的解决方案,我还没有看到?
  • @keyboard 我不清楚你遇到的问题。你能说得更具体些,或许可以举个例子?
  • 如果我们有 Component 的向量图,我们可以遍历所有的 Components 并调用基调用 Component 所具有的函数。但是,如果我们有一个 void 映射(实际上它背后有多个不同类型的向量),我们永远无法迭代所有组件并调用一些基函数类。我希望这次我能解释得更好。
  • @keyboard 好吧,每次你想使用它的时候都需要正确地转换它。例如map_wrapper.get&lt;B&gt;(20)表示获取键20对应的map元素,并将其转换为vector&lt;B&gt;。所以,返回类型是一个向量,这就是为什么我可以写一个像 for(auto const&amp; x: map_wrapper.get&lt;B&gt;(20)) ... 这样的循环
【解决方案3】:

解决方案是使用 reinterpret_cast:

    template<class T> inline std::vector<std::shared_ptr<T>> * getVector() const
    {
        auto key = getKey<T>();
        return reinterpret_cast<std::vector<std::shared_ptr<T>> *>( (_componentMap.count(key) ? _componentMap.at(key).get() : const_cast<std::vector<std::shared_ptr<Component>> *>(&_emptyComponentSharedPtrVec)) );
    }

它不是很漂亮,但它确实可以正常工作并且满足所有要求。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-30
    • 1970-01-01
    • 2011-02-11
    • 2021-09-20
    • 1970-01-01
    相关资源
    最近更新 更多