C++ 克服C+中缺乏模板化虚拟函数的问题+;

C++ 克服C+中缺乏模板化虚拟函数的问题+;,c++,c++11,templates,C++,C++11,Templates,我不确定如何最好地表达这个问题,但我不是在问如何实现模板虚拟函数本身。我正在构建一个实体组件系统,我有两个重要的类-World和entity。World实际上是一个抽象类,实现(我们称之为WorldImpl)是一个模板类,允许使用自定义分配器(可以与std::allocator\u traits一起使用) 组件是我们可以附加到实体的任何数据类型。这是通过对实体调用名为assign的模板函数来实现的 问题是:我试图让实体在创建和初始化组件时使用世界上的分配器。在理想情况下,您可以调用Entity:

我不确定如何最好地表达这个问题,但我不是在问如何实现模板虚拟函数本身。我正在构建一个实体组件系统,我有两个重要的类-
World
entity
。World实际上是一个抽象类,实现(我们称之为
WorldImpl
)是一个模板类,允许使用自定义分配器(可以与
std::allocator\u traits
一起使用)

组件是我们可以附加到实体的任何数据类型。这是通过对实体调用名为
assign
的模板函数来实现的

问题是:我试图让实体在创建和初始化组件时使用世界上的分配器。在理想情况下,您可以调用
Entity::assign(…)
,这将要求
WorldImpl
使用任何合适的分配器创建组件。然而,这里有一个问题-实体有一个指向
World
的指针,据我所知,模板化的虚拟函数是不可能的

下面是一个更能说明问题的例子:

class Entity
{
  template<typename ComponentType>
  void assign(/* ... */)
  {
    /* ... */
    ComponentType* component = world->createComponent<ComponentType>(/* ... */);
    /* ... */
  }

  World* world;
};

// This is the world interface.
class World
{
  // This is the ideal, which isn't possible as it would require templated virtual functions.
  template<typename ComponentType>
  virtual ComponentType* createComponent(/* ... */) = 0;
};

template<typename Allocator>
class WorldImpl : public World
{
  template<typename ComponentType> // again, not actually possible
  virtual ComponentType* createComponent(/* ... */)
  {
    // do something with Allocator and ComponentType here
  }
};
类实体
{
模板
无效分配(/*…*/)
{
/* ... */
ComponentType*component=world->createComponent(/*…*/);
/* ... */
}
世界*世界;
};
//这是世界界面。
阶级世界
{
//这是理想的,但这是不可能的,因为它需要模板化的虚拟函数。
模板
虚拟组件类型*createComponent(/*…*/)=0;
};
模板
WorldImpl类:公共世界
{
模板//同样,实际上不可能
虚拟组件类型*createComponent(/*…*/)
{
//在这里对分配器和ComponentType执行一些操作
}
};

鉴于上面的代码实际上是不可能的,这里有一个真正的问题:对于这样的类层次结构,为了使用ComponentType和分配器模板参数调用某个函数,我必须做些什么?这是最终的目标-一个在某个对象上调用的函数,它具有两个可用的模板参数。

我想说的是,实体属于某种类型的世界,并使它们成为具有世界参数的模板。然后,您可以忘记所有继承和
虚拟
,只实现满足所需接口的世界,例如

template<typename World>
class Entity
{
  template<typename ComponentType>
  void assign(/* ... */)
  {
    /* ... */
    ComponentType* component = world.createComponent<ComponentType>(/* ... */);
    /* ... */
  }

  World world;
};

template<typename Allocator>
class WorldI
{
  template<typename ComponentType>
  ComponentType* createComponent(/* ... */)
  {
    // do something with Allocator and ComponentType here
  }
};
模板
类实体
{
模板
无效分配(/*…*/)
{
/* ... */
ComponentType*component=world.createComponent(/*…*/);
/* ... */
}
世界;
};
模板
世界一级
{
模板
ComponentType*createComponent(/*…*/)
{
//在这里对分配器和ComponentType执行一些操作
}
};

请注意,这不是一个最佳的解决方案(有关问题,请参阅本文底部),而是一种结合模板和虚拟函数的可行方法。我把它贴出来,希望你能以此为基础,想出更有效的方法。如果您找不到改进的方法,我建议按照建议模板化
实体


如果您不想对
实体
进行任何重大修改,您可以在
世界
中实现隐藏的虚拟辅助功能,以实际创建组件。在这种情况下,helper函数可以获取一个参数,该参数指示要构造的组件类型,并返回
void*
createComponent()
调用隐藏函数,指定
ComponentType
,并将返回值强制转换为
ComponentType*
。我能想到的最简单的方法是为每个组件提供一个静态成员函数,
create()
,并将类型索引映射到
create()
调用

为了允许每个组件接受不同的参数,我们可以使用一个helper类型,我们称它为
参数
。这种类型在包装实际参数列表时提供了一个简单的接口,允许我们轻松定义
create()
函数

// Argument helper type.  Converts arguments into a single, non-template type for passing.
class Arguments {
  public:
    struct ArgTupleBase
    {
    };

    template<typename... Ts>
    struct ArgTuple : public ArgTupleBase {
        std::tuple<Ts...> args;

        ArgTuple(Ts... ts) : args(std::make_tuple(ts...))
        {
        }

        // -----

        const std::tuple<Ts...>& get() const
        {
            return args;
        }
    };

    // -----

    template<typename... Ts>
    Arguments(Ts... ts) : args(new ArgTuple<Ts...>(ts...)), valid(sizeof...(ts) != 0)
    {
    }

    // -----

    // Indicates whether it holds any valid arguments.
    explicit operator bool() const
    {
        return valid;
    }

    // -----

    const std::unique_ptr<ArgTupleBase>& get() const
    {
        return args;
    }

  private:
    std::unique_ptr<ArgTupleBase> args;
    bool valid;
};
然后,在所有这些就绪的情况下,我们最终可以实现
实体
世界
、和
世界impl

// This is the world interface.
class World
{
    // Actual worker.
    virtual void* create_impl(const std::type_index& ctype, const Arguments& arg_holder) = 0;

    // Type-to-create() map.
    static std::unordered_map<std::type_index, std::function<void*(const Arguments&)>> creators;

  public:
    // Templated front-end.
    template<typename ComponentType>
    ComponentType* createComponent(const Arguments& arg_holder)
    {
        return static_cast<ComponentType*>(create_impl(typeid(ComponentType), arg_holder));
    }

    // Populate type-to-create() map.
    static void populate_creators() {
        creators[typeid(One)] = &One::create;
        creators[typeid(Two)] = &Two::create;
    }
};
std::unordered_map<std::type_index, std::function<void*(const Arguments&)>> World::creators;


// Just putting in a dummy parameter for now, since this simple example doesn't actually use it.
template<typename Allocator = std::allocator<World>>
class WorldImpl : public World
{
    void* create_impl(const std::type_index& ctype, const Arguments& arg_holder) override
    {
        return creators[ctype](arg_holder);
    }
};

class Entity
{
    World* world;

  public:
    template<typename ComponentType, typename... Args>
    void assign(Args... args)
    {
        ComponentType* component = world->createComponent<ComponentType>(Arguments(args...));

        std::cout << *component;

        delete component;
    }

    Entity() : world(new WorldImpl<>())
    {
    }

    ~Entity()
    {
        if (world) { delete world; }
    }
};

int main() {
    World::populate_creators();

    Entity e;

    e.assign<One>();
    e.assign<Two>();

    e.assign<One>(118, true);
    e.assign<Two>('?', 8.69);

    e.assign<One>('0', 8);      // Fails; calls something like One(1075929415, true).
    e.assign<One>((int)'0', 8); // Succeeds.
}
//这是世界界面。
阶级世界
{
//实际工人。
虚拟void*create_impl(const std::type_index&ctype,const参数&arg_holder)=0;
//Type-to-create()映射。
静态std::无序的地图创建者;
公众:
//模板化前端。
模板
ComponentType*createComponent(常量参数和参数持有者)
{
返回静态类型转换(创建impl(typeid(ComponentType),arg_holder));
}
//填充type-to-create()映射。
静态空白填充_创建者(){
创建者[typeid(一)]=&One::创建;
创建者[typeid(两个)]=&Two::create;
}
};
std::无序地图世界::创造者;
//现在只需要输入一个伪参数,因为这个简单的示例实际上并不使用它。
模板
WorldImpl类:公共世界
{
void*create_impl(const std::type_index和ctype、const参数和arg_holder)重写
{
返回创建者[ctype](参数持有者);
}
};
类实体
{
世界*世界;
公众:
模板
无效分配(Args…Args)
{
ComponentType*component=world->createComponent(参数(args…);

std::cout CRTP,也许?嗯,我以前没有见过这种模式。这可能会起作用,我必须做一些工作。谢谢你的提示!有几个问题:我猜实体将拥有或至少有指向组件的指针?这不意味着组件也有基类;因为实体没有模板化?谁将拥有组件如果不是实体?将分配与构造分开。
allocate
可以是一个虚拟的非模板函数,
createComponent
可以是一个非虚拟的函数模板。@SamBloomberg可以忽略分配器特征的构造/析构函数方法,而只使用placement new/manual Distruction。使用allo
// This is the world interface.
class World
{
    // Actual worker.
    virtual void* create_impl(const std::type_index& ctype, const Arguments& arg_holder) = 0;

    // Type-to-create() map.
    static std::unordered_map<std::type_index, std::function<void*(const Arguments&)>> creators;

  public:
    // Templated front-end.
    template<typename ComponentType>
    ComponentType* createComponent(const Arguments& arg_holder)
    {
        return static_cast<ComponentType*>(create_impl(typeid(ComponentType), arg_holder));
    }

    // Populate type-to-create() map.
    static void populate_creators() {
        creators[typeid(One)] = &One::create;
        creators[typeid(Two)] = &Two::create;
    }
};
std::unordered_map<std::type_index, std::function<void*(const Arguments&)>> World::creators;


// Just putting in a dummy parameter for now, since this simple example doesn't actually use it.
template<typename Allocator = std::allocator<World>>
class WorldImpl : public World
{
    void* create_impl(const std::type_index& ctype, const Arguments& arg_holder) override
    {
        return creators[ctype](arg_holder);
    }
};

class Entity
{
    World* world;

  public:
    template<typename ComponentType, typename... Args>
    void assign(Args... args)
    {
        ComponentType* component = world->createComponent<ComponentType>(Arguments(args...));

        std::cout << *component;

        delete component;
    }

    Entity() : world(new WorldImpl<>())
    {
    }

    ~Entity()
    {
        if (world) { delete world; }
    }
};

int main() {
    World::populate_creators();

    Entity e;

    e.assign<One>();
    e.assign<Two>();

    e.assign<One>(118, true);
    e.assign<Two>('?', 8.69);

    e.assign<One>('0', 8);      // Fails; calls something like One(1075929415, true).
    e.assign<One>((int)'0', 8); // Succeeds.
}