假设:
- 你的问题是an XY problem,你只认为存储指向不同类型向量的指针可能是一个解决方案而不是解决方案,并且
- 您在编译时就知道组件类型列表(而不是允许运行时注册),
那么这可以通过一个元组和一些迭代它的机制来相对容易地实现。
首先,我们希望元函数在给定组件类型列表的情况下生成正确的元组类型:
namespace detail {
template<std::size_t N, typename... Components>
std::tuple<std::array<Components, N>...>
makeComponentPool(std::tuple<Components...>) noexcept;
} // namespace detail
template<std::size_t N, typename ComponentTup>
using ComponentPool = decltype(detail::makeComponentPool<N>(std::declval<ComponentTup>()));
// example:
static_assert(std::is_same<
ComponentPool<10, std::tuple<AIComponent, PhysicsComponent, RenderComponent>>,
std::tuple<
std::array<AIComponent, 10>,
std::array<PhysicsComponent, 10>,
std::array<RenderComponent, 10>
>
>::value);
然后我们需要一些迭代元组的方法; boost::fusion::for_each 在这里工作得很好,或者我们可以自己滚动:
namespace detail {
template<typename TupT, typename FunT, std::size_t... Is>
void for_each(TupT&& tup, FunT&& f, std::index_sequence<Is...>) {
using expand = int[];
(void)expand{0, (f(std::get<Is>(std::forward<TupT>(tup))), void(), 0)...};
}
} // namespace detail
template<
typename TupT, typename FunT,
std::size_t TupSize = std::tuple_size<std::decay_t<TupT>>::value
>
void for_each(TupT&& tup, FunT&& f) {
detail::for_each(
std::forward<TupT>(tup), std::forward<FunT>(f),
std::make_index_sequence<TupSize>{}
);
}
现在我们需要做出决定:每种组件类型是否应该具有相同的公共访问点?在问题中有update()和render();但是,如果我们可以给这些都起相同的名字(例如process()),那么事情就很简单了:
struct AIComponent { void process() { } };
struct PhysicsComponent { void process() { } };
struct RenderComponent { void process() { } };
class Game {
using ComponentTypes = std::tuple<AIComponent, PhysicsComponent, RenderComponent>;
static constexpr std::size_t MAX_NUM = 3;
ComponentPool<MAX_NUM, ComponentTypes> componentPool;
std::atomic_bool gameOver{false};
public:
void runGame() {
while (!gameOver) {
for_each(componentPool, [](auto& components) {
for (auto& component : components) {
component.process();
}
});
}
}
void endGame() { gameOver = true; }
};
Online Demo
(演示中的 Nb process() 仅提供了一个参数用于说明,而不是由于任何实现要求。)
现在您只需要管理MAX_NUM 和ComponentTypes,其他一切都已到位。
但是,如果您想为不同的组件类型允许不同的访问点(例如,update() 用于AIComponent 和PhysicsComponent,但render() 用于RenderComponent,如问题所示)那么我们显然有一个还有一些工作要做。一种方法是为调用访问点添加一个间接级别,并且以最小的开销(语法和运行时)干净地完成此操作的一种方法是使用一些实用程序来创建本地重载集,以便组件处理可以非常特殊-按类型区分。这是一个适用于所有仿函数(包括 lambda)但不适用于函数指针的基本实现:
template<typename FunT, typename... FunTs>
struct overloaded : private FunT, private overloaded<FunTs...> {
overloaded() = default;
template<typename FunU, typename... FunUs>
overloaded(FunU&& f, FunUs&&... fs)
: FunT(std::forward<FunU>(f)),
overloaded<FunTs...>(std::forward<FunUs>(fs)...)
{ }
using FunT::operator();
using overloaded<FunTs...>::operator();
};
template<typename FunT>
struct overloaded<FunT> : private FunT {
overloaded() = default;
template<typename FunU>
overloaded(FunU&& f) : FunT(std::forward<FunU>(f)) { }
using FunT::operator();
};
template<typename... FunTs>
overloaded<std::decay_t<FunTs>...> overload(FunTs&&... fs) {
return {std::forward<FunTs>(fs)...};
}
如果需要,可以在网上找到更强大的 overload 实现,但即使有了这个简单的实现,我们现在也可以执行以下操作:
struct AIComponent { void update() { } };
struct PhysicsComponent { void update() { } };
struct RenderComponent { void render() { } };
class Game {
using ComponentTypes = std::tuple<AIComponent, PhysicsComponent, RenderComponent>;
static constexpr std::size_t MAX_NUM = 3;
ComponentPool<MAX_NUM, ComponentTypes> componentPool;
std::atomic_bool gameOver{false};
public:
void runGame() {
// `auto` overload is the least specialized so `update()` is the default
static auto process = overload(
[]( auto& comp) { comp.update(); },
[](RenderComponent& comp) { comp.render(); }
);
while (!gameOver) {
for_each(componentPool, [](auto& components) {
std::for_each(begin(components), end(components), process);
// alternatively, equivalently:
//for (auto& component : components) {
// process(component);
//}
});
}
}
void endGame() { gameOver = true; }
};
Online Demo
现在您需要管理MAX_NUM 和ComponentTypes,并且还可能向process 添加一个新的重载(但如果您忘记了会出现编译器错误)。