6

我创建了一个物理系统,可以处理任何碰撞对象到任何碰撞对象,如下所示:

namespace Collision
{
    template <typename T, typename U>
    inline void Check(T& t, U& u)
    {
        if(u.CheckCollision(t.GetCollider()))
        {
            u.HitBy(t);
            t.Hit(u);
        }
    }
}

还有其他几个帮助对象使其易于使用,但要点是有动态对象需要针对静态对象和其他动态对象进行测试,而静态对象不需要检查。

我想要的是这样的:

void func()
{
    PhysicsWorld world;
    shared_ptr<CSphere> ballPhysics(new CSphere(0,0,ballSprite->Width()));
    BallCommand ballBehavior;
    CBounds bounds(0, 0, 640, 480);
    CBox obstacle(200, 150, 10, 10);

    Collision::Collidable<CBounds> boundC(bounds);
    Collision::Collidable<std::shared_ptr<CSphere>, BallCommand&> ballC(ballPhysics, ballBehavior);
    Collision::Collidable<CBox> obstC(obstacle);

    world.addStatic(boundC);
    world.addDynamic(ballC);
    world.addStatic(obstC);
    ...
    ...
    world.Update();
    ...
    ...
}

我很想通过添加函数来推断容器,因此使用系统会自动更新类型列表。我想我知道如何使用模板函数生成类型列表,但不知道如何在我需要的地方获得它,或者在编译的什么时候完成。

如果不是这样,那么某些系统使用两个类型列表,然后在内部编写更新函数来迭代所有列表,使它们相互配对。

我已经阅读了一些 boost MPL 的书,并多次阅读了 Andrei 的书。但是,我似乎陷入了它的工作原理,并没有真正将其转化为我如何使用它。我希望他们在 MPL 书中有更多关于现实世界示例的部分。

我已经能够让游戏引擎的所有部分与渲染、物理、碰撞(我将检测与反应分开)、输入、网络、声音等以通用方式进行交互。现在我只需要以一种通用的方式来保存所有的东西。在完成了所有通用工作之后,要求继承只是为了让我可以在容器中保存一些东西是很愚蠢的,而且我不想手动编写每个集合的可能性,因为这是通用编程的一大好处。

我看到 Jalf 曾表示他/她使用 MPL 做了类似的事情,但没有详细说明,我无法弄清楚。如果有人知道实际使用示例或者我可以在哪里获得有关使用 MPL 的更多信息,我将不胜感激。

再次感谢!

更新

boost MPL 和 boost Fusion 似乎都可以满足我的要求,但是这两个库的真实生活示例似乎都很少。MPL 的文档只不过是这个模板所做的,祝你好运理解它的含义。“这是一个例子,但这只是冰山一角!” Fusion 更好一些。

一个典型的 boost MPL 示例是 has_xxx。他们在示例中使用了 XXX 和 xxx,因此很难看出可以使用 XXX(所需文本)和 Test 或 CheckType 或任何更可区分的用户类型来代替 xxx 的区别。另外,没有提到这些都不在命名空间中。现在我知道为什么斯科特迈耶斯将其与《惊魂记》中的淋浴场景相提并论了。

真的很遗憾,因为我编译和理解的一点点确实有用的东西,但是很难弄清楚如果我在运输产品上我永远不会花这么多精力。

如果有人知道真实世界的示例或更好的参考资料、解释或教程,我将不胜感激。

更新

这里有更多代码:

template <typename T, typename V = VictimEffect, typename M = MenaceEffect>
class Collidable
{
    T m_Collider;
    V m_HitBy;
    M m_Hit;

public:
    Collidable(T collide, V victim, M menace) : m_Collider(collide), m_HitBy(victim),         m_Hit(menace) {;}
    Collidable(T collide) : m_Collider(collide) {;}
    Collidable(T collide, V victim) : m_Collider(collide), m_HitBy(victim) {;}

    T& GetCollider()
    {
        return m_Collider;
    }

    template <typename V>
    void HitBy(V& menace)
    {
        m_HitBy.HitBy(menace.GetCollider());
    }

    template <typename V>
    void Hit(V& victim)
    {
        m_Hit.Hit(victim.GetCollider());
    }

    template <typename V>
    bool CheckCollision(V& menace)
    {
        return m_Collider.CheckCollision(menace);
    }
};

然后使用它我这样做

    Collidable<Boundary, BallCommand> boundC(boundary, ballBehavior);
    Collidable<CollisionBox> ballC(circle);

然后我所需要的就是调用与我所有的主动可碰撞对象碰撞我所有的主动和被动对象。

我没有使用 std::function ,因为添加函数名称使代码对我来说更清晰。但也许这只是传统思维。

4

2 回答 2

3

如果我理解正确,您的问题是:

class manager {
public:
    template<typename T>
    void add(T t);

private:
    /* ??? */ data;
    /* other members? */
};

manager m;
some_type1 s1;
some_type2 s2;
m.add(s1);
m.add(s2);
/* m should hold its copies of s1 and s2 */

其中 some_type1 和 some_type2 是不相关的,并且您不愿意重新设计它们以使用动态多态性。

我认为 MPL 或 Fusion 都不会用这种形式做你想做的事。如果您的问题是使用什么容器作为 的成员PhysicsWorld,那么任何编译时计算都无济于事:成员类型是在实例化时确定的,即行manager m;

可以以某种元编程方式重写管理器,以便以这种方式使用它:

typedef manager<> m0_type;
typedef typename result_of::add<m0_type, some_type1>::type m1_type;
typedef typename result_of::add<m1_type, some_type2>::type final_type;
/* compile-time computations are over: time to instantiate */
final_type m;
/* final_type::data could be a tuple<some_type1, some_type2> for instance */
m.add(s1); m.add(s2);

这确实是 MPL+Fusion 可以帮助的事情。然而,这仍然在编译时世界中非常固定:你能想象编写一个template<typename Iter> void insert(Iter first, Iter last)just 以便将容器的内容复制到管理器中吗?

请允许我假设您的要求是这样的,实际上manager必须以更实时的方式使用,就像我最初对您的问题的表述一样。(我不认为这对于 a 来说是一个很大的想象空间PhysicsWorld)。有一个替代方案,我认为它更合适,更简洁且更易于维护:类型擦除。(该技术的名称可能有点不幸,第一次可能会产生误导。)

类型擦除的一个很好的例子是 std::function:

std::function<void()> func;
func = &some_func; /* this just looks like that internally std::function stores a void(*)() */
func = some_type(); /* but here we're storing a some_type! */

类型擦除是一种将编译时与运行时联系起来的技术:在上面的两个赋值中,参数都是不相关的类型(其中一个是非类的,因此甚至不是远程运行时多态的),但 std::function 处理两者,只要它们履行它们可以用作的合同f()(其中 f 是相应类型的实例)并且表达式具有类型 (convertible to) void。这里的契约是类型擦除的编译时方面。

我不打算演示如何实现类型擦除,因为在Boostcon 2010 上有一个关于该主题的精彩演示。(您可以通过链接观看演示和/或获取幻灯片)。或者我(或其他人)可以在评论中做到这一点。

最后一点,类型擦除的实现(通常)使用动态多态性。我提到这一点是因为我注意到您考虑将类型列表用作存储为manager成员的运行时对象。这闻起来像是穷人的反射,实际上,是穷人的动态多态性。所以请不要那样做。如果您的意思是 MPL 计算结果中的类型列表,则忽略该节点。

于 2011-04-26T23:49:18.503 回答
1

这不完整而且我没有得到我想要的一切,但现在已经足够好了。我正在输入整个解决方案,以防它帮助其他人。

#include <boost\mpl\vector.hpp>
#include <boost\mpl\fold.hpp>
#include <boost\mpl\for_each.hpp>
#include <boost\mpl\inherit.hpp>
#include <boost\mpl\inherit_linearly.hpp>
#include <iostream>
#include <vector>

using namespace boost::mpl::placeholders;

typedef boost::mpl::vector<short, long, char, int> member_types;

template <typename T>
struct wrap
{
    std::vector<T> value;
};

typedef boost::mpl::inherit_linearly<member_types, boost::mpl::inherit<wrap<_2>, _1> >::type Generate;

class print
{
    Generate generated;

public:
    template <typename T>
    void operator()(T)
    {
        std::cout << *static_cast<wrap<T>&>(generated).value.begin() << std::endl;
    }

    template <typename T>
    void Add(T const& t)
    {
        static_cast<wrap<T>&>(generated).value.push_back(t);
    }
};

void main()
{
    print p;

    short s = 5;
    p.Add(s);
    long l = 555;
    p.Add(l);
    char c = 'c';
    p.Add(c);
    int i = 55;
    p.Add(i);

    boost::mpl::for_each<member_types>(p);
}

这不是我需要的最终对象,但现在我拥有了所有部件来制作我想要的东西。

更新

最后我明白了。

template <typename TL>
class print
{
    template <typename T>
    struct wrap
    {
        std::vector<T> value;
    };

    typedef typename boost::mpl::inherit_linearly<TL, boost::mpl::inherit<wrap<_2>, _1> >::type Generate;
    Generate generated;

public:
    void Print()
    {
        boost::mpl::for_each<TL>(*this);
    }

    template <typename T>
    void operator()(T)
    {
        std::cout << *static_cast<wrap<T>&>(generated).value.begin() << std::endl;
    }

    template <typename T>
    void Add(T const& t)
    {
        static_cast<wrap<T>&>(generated).value.push_back(t);
    }
};

这里的 TL 是一个 boost::mpl 容器,其中包含应该保存的类型。

我认为这为扩展提供了一个很好的起点,但涵盖了大部分元编程部分。

我希望这对其他人有帮助。

于 2011-05-02T11:09:26.637 回答