2

我有一个基于“实体”的程序,它包含“组件”(组合 FTW)。

组件可能包括许多不同的类型,包括脚本、资产等。我想构建一个名为的实体函数

实体具有字符串映射和实际组件,因此可以按类型名称搜索组件。

我想要一个名为

<Component T>GetComponent(char* TypeName, <T>);

它接受一个字符串和一个类型名称,并返回所请求的类型化组件。

有可能用 C++ 模板做这样的事情吗?以上显然不起作用,我不知道该怎么做。

谢谢

编辑:

我不是在找工厂。

实体持有不同类型组件的实例。目前这是通过

std::vector<Component> componentList; 

std::vector<char*> componentNames; 

保证其索引相同。可能我稍后会写一张合适的地图。

我只是想让 GetComponent 返回一个正确类型的引用,该引用对 ComponentList 中的 Entity 持有的类型名称已经实例化的组件。

4

4 回答 4

11

您的函数是否创建组件?然后是工厂。您可以将其包装在该模板中,以便为客户节省(可能是错误的)强制转换。

函数模板的类型如下所示:

template< typename T >
T* GetComponent(const char*); // presuming it returns a pointer

它会被这样调用:

Foo* foo = GetComponent<Foo>("foo");
于 2009-11-08T21:46:13.310 回答
3

提出正确的问题至少是获得好答案的一半。您应该真正说明您想要实现的目标,而不是您面临的特定问题。在我看来,您的语言问题似乎比您实际意识到的要多。

第一部分是您似乎有一个派生自 Component 的组件层次结构,可能提供了一个通用接口。一个实体在内部拥有许多组件,它们可以是任何从 Component 派生的类型。如果是这种情况,您应该在直接存储 Component 对象时重新设计您的容器,这将在您的对象中产生切片(无论您在容器中输入什么派生类型,容器都只会保留常见的 Component 部分目的)。

处理几个向量并希望它们始终保持同步是可行的,但很脆弱。如果名称和组件一起出现,那么您希望存储成对的名称/组件。如果要按名称搜索,则应使用地图,因为它将直接提供 O(log N) 搜索。

现在,回到问题。如果您想要实现的是简单的语法糖(如果需要,请避免调用者显式动态转换),那么您可以使用模板获得它(稍后会详细介绍)。但是你真的应该考虑你的设计。Component 是否定义了任何组件的真实接口?如果用户在使用组件之前需要向下转换为特定类型,则要么抽象不好(组件不提供真正的接口),要么对象并没有真正组合在一起。

如果在它结束时您仍然想这样做,您可以通过在模板方法(或自由函数)中执行它来向调用者隐藏动态转换。

class Entity {
   typedef std::map< std::string, boost::shared_ptr<Component> > component_map_t;
public:
   boost::shared_ptr<Component> getComponent( std::string const & name ) {
      component_map_t::iterator it = components_.find(name);
      if ( it == components_.end() ) { // not found, handle error
         // ...
      }
      return it->second;
   }
   template <typename T> // syntactic sugar
   boost::shared_ptr<T> getComponent( std::string const & name ) {
      return boost::dynamic_pointer_cast<T>( getComponent(name) );
   }
private:
   component_map_t components_;
};
template <typename T> // syntactic sugar also available as free function
boost::shared_ptr<T> getComponent( Entity & entity, std::string const & name ) {
   return boost::dynamic_pointer_cast<T>( entity.getComponent(name) );
}
int main() { // usage
   Entity e; // ... work with it add components...
   boost::shared_ptr<Component> c1 = e.getComponent( "one" ); // non-templated method returns Component
   boost::shared_ptr<DerivedComponent> c2 = e.getComponent<DerivedComponent>( "two" );
   boost::shared_ptr<DerivedComponent> c3 = getComponent<DerivedComponent>( e, "two" );
}

您可以使用界面,而不是boost::shared_ptr返回真正的引用(它所需要的:必须仔细控制生命周期,以便如果从实体中删除组件,用户代码不会尝试使用悬空引用)。

于 2009-11-08T23:30:49.433 回答
1

你可以使用类似的东西:

struct Entity
{
    Component* GetBaseComponent (const char* TypeName)
    {
        // do lookup on TypeName and return corresponding Component.
    }

    template<typename T> T* GetComponent (const char* TypeName)
    {
        return dynamic_cast<T*> (GetBaseComponent (TypeName));
    }
};

并用以下方式调用它:

entity.GetComponent<MyComponent> ("MyComponent");

如果您要求一个组件并且类型错误,则强制转换将返回一个空 ptr。

编辑:刚刚意识到这与 sbi 基本上是相同的解决方案,尽管没有将其称为工厂。

于 2009-11-08T23:21:39.053 回答
0

您的 getComponent 函数有两个单独的任务

1) 从字符串标识符中检索对象

2) 将此对象转换为提供的模板参数类型

使用模板使 (2) 变得非常简单。但是 (1) 需要对字符串对象进行处理,因此模板无法自行解决问题。您必须以其他方式填充组件容器。至于存储和转换,您可能对 boost::any 或 boost::variant 感兴趣。

于 2009-11-08T23:10:09.520 回答