示例情况:我正在创建一个用于游戏开发的物理引擎。我有两种类型的空间划分方法:
- 网格
- 四叉树
我想允许程序员使用我的物理引擎:
-
在编译时通过模板参数
选择空间分区方法Physics::World<Physics::Grid> world; // chosen at compile-time
-
在运行时通过多态对象选择空间分区方法
Physics::WorldRunTime world; world.setSpatialPartitioningMethod(new Physics::Grid); // chosen at run-time
从上面的例子中可以看出,我必须使用两个不同的类(
World
和WorldRunTime
)。这将导致代码重复,因为我将有一个没有运行时多态性的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
这是目前为止我能想到的最好的:
这样做的一个很大的缺点是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。不知道它是否能在其他编译器上工作