允许运行时和编译时多态性的灵活方式



示例情况:我正在创建一个用于游戏开发的物理引擎。我有两种类型的空间划分方法:

    网格
  1. 四叉树

我想允许程序员使用我的物理引擎:

  1. 在编译时通过模板参数

    选择空间分区方法
    Physics::World<Physics::Grid> world; // chosen at compile-time
    
  2. 在运行时通过多态对象选择空间分区方法

    Physics::WorldRunTime world; 
    world.setSpatialPartitioningMethod(new Physics::Grid); // chosen at run-time
    

从上面的例子中可以看出,我必须使用两个不同的类(WorldWorldRunTime)。这将导致代码重复,因为我将有一个没有运行时多态性的World模板类和一个具有运行时多态性的WorldRunTime模板类。

这是可以的。

我,然而,想知道是否有一个模式来处理这个问题,允许程序员传递一些东西作为模板参数(编译时多态性)或多态对象(运行时多态性)与最小的代码重复。

某种策略模式,可以在编译时或运行时选择策略?


所需示例(?)代码:

{
    // *** Compile-time world
    // No overhead
    Physics::World<Physics::Policy::CompileTime, Physics::Grid> world;
}
{
    // *** Run-time world
    // Allows run-time spatial partitioning method swapping
    // Suffers from run-time polymorphism overhead
    Physics::World<Physics::Policy::RunTime> world;
    world.setSpatialPartitioningMethod(new Physics::Grid);
}

注意在两个块中我是如何使用同一个类的,以避免不得不编写两个类和重复的代码。是否有任何方法可以实现类似上述代码的东西?

对于运行时多态性,使用派生各种具体策略的多态基类实例化模板。

为了鼓励在不使用运行时多态性时去虚拟化,将具体的策略类标记为final

我认为只要您愿意将Physics::Policy::CompileTime作为模板,这是可以实现的。这是完全未编译的,因为我没有足够的代码来尝试编译一个真正的用例。

template <typename Child>
struct CompileTime
{
    void operation1(int v) { child_.operation1(v); }
    void setSpatialPartitioningMethod(PhysicsPolicy* policy) {} // No work on static policy.
    Child child_;
};
struct RunTime
{
    RunTime() : policy_(0) {}
    void setSpatialPartitioningMethod(PhysicsPolicy* policy) { policy_ = policy; }
    void operation1(int v) { policy_->operation1(v); }
    PhysicsPolicy* policy_;
};
template <typename Policy>
class World
{
public:
    void setSpatialPartitioningMethod(PhysicsPolicy* policy) { policy_.setSpatialPartitioningMethod(policy); }
private:
    Policy policy_;
};

{
    // *** Compile-time world
    // No overhead
    Physics::World<Physics::Policy::CompileTime<Physics::Grid> > world;
}
{
    // *** Run-time world
    // Allows run-time spatial partitioning method swapping
    // Suffers from run-time polymorphism overhead
    Physics::World<Physics::Policy::RunTime> world;
    world.setSpatialPartitioningMethod(new Physics::Grid);
}

作为一个心理练习,我想看看我是否可以在一个额外的约束下尽可能接近您想要的语法:如果选择编译时间,我不想产生vtbl指针或vtbl本身的成本。到目前为止,这里的其他解决方案将在运行时或编译时正确绑定,但即使您选择Policy::CompileTime

,它们仍然会生成vtbl并将World对象的大小增加sizeof(指针)。

这是目前为止我能想到的最好的:

这样做的一个很大的缺点是Grid变成了一个模板类,因为我需要让它有选择地从PhysicsPolicy继承(以避免生成vtbl)。

在你想要的语法和下面的东西之间有一个小的delta:我需要用一个空的模板列表实例化运行时网格,即。new Physics::Grid<>

我已经重写了operator->,这样world->findObject()将调用该方法,而不必编写一大批额外的方法——另一种选择是在Policy::Runtime中为physicsppolicy中的每个方法添加一个定义。

struct Point3 { float x, y, z; };
namespace Physics
{
    // This is a dummy base class used to avoid vtbl creation for Grid
    class Empty { };
    class PhysicsPolicy
    {
    public:
        virtual void* findObject(const Point3& p) = 0;
    };
    template<typename BASE=PhysicsPolicy>
    class Grid : public BASE
    {
    public:
        void* findObject(const Point3& p) { return nullptr; } // Just a placeholder
    };
    namespace Policy
    {
        template<template<typename> class T> class CompileTime
        {
        public:
            T<Empty>* operator->() { return &obj; }
        private:
            T<Empty> obj;
        };
        // This is just here so that an empty template parameter list is possible
        template<template<typename> class... T> class RunTime;
        template<> class RunTime<>
        {
        public:
            void setSpatialPartitioningMethod(PhysicsPolicy* aP) { p = aP; }
            PhysicsPolicy* operator->() { return p; }
        private:
            PhysicsPolicy* p;
        };
    }
    // The desired syntax has the number of parameters dependent upon the policy
    // This is done using c++11's variadic templates, and passing the extra
    // parameters into Binding
    template<
        template<template<typename> class...> class Binding,
        template<typename> class... U>
    class World : public Binding<U...>
    {
    public:
    };
}
int main()
{
    Physics::World<Physics::Policy::CompileTime, Physics::Grid> compileTimeWorld;
    Point3 p{1,2,3};
    printf("%pn", compileTimeWorld->findObject(p));
    Physics::World<Physics::Policy::RunTime> runTimeWorld;
    runTimeWorld.setSpatialPartitioningMethod(new Physics::Grid<>);
    printf("%pn", runTimeWorld->findObject(p));
    return 0;
}

注意:测试了clang。不知道它是否能在其他编译器上工作

最新更新