0

我正在从文件中加载不同类型的派生类,其形式为:

  • 4 字节类 ID 标头
  • n字节序列化数据

每个类都继承自同一个基类,但我在优雅地创建它们时遇到了一些麻烦。这是我当前的代码(这里 Foo 和 Bar 继承自同一类型):

// read class ID
uint32_t id = ReadHeader(myFile);

// create correct class
switch (id)
{
    case ID_CLASS_FOO: myClass = new Foo(myFile); break;
    case ID_CLASS_BAR: myClass = new Bar(myFile); break;
    /* ... */
}

但我发现这相当丑陋、乏味且容易出错,因为对于我添加的每一个额外的类,我都需要一个额外的定义/枚举成员,并在 switch 中增加一行。

我正在寻找的是我会声明一个编译时“类型数组”的东西,如下所示:

ClassTypes = {Foo, Bar, ...};

然后,在读取文件时,只需执行以下操作:

myClass = new ClassTypes[id](myFile);

有没有办法在 C++ 中做到这一点?

4

3 回答 3

2

您可以创建一个工厂类。

工厂定义:

typedef ClassType* (*ClassCreation(void))

class ClassFactory
{
    private:
        map<ClassId, ClassCreation> creators;

    public:
        ClassFactory()
        {
            creators[ID_CLASS_FOO] = &Foo::create;
            creators[ID_CLASS_BAR] = &Bar::create;
        }

        ClassType* getClassType(ClassId id)
        {
            return (creators[id])()
        }
};

class ClassType
{
    //etc
};

class Foo : public ClassType
{
    public:
        static ClassType* create()
        {
            return new Foo;
        }
};

class Bar : public ClassType
{
    public:
        static ClassType* create()
        {
            return new Bar;
        }
};

工厂用途:

ClassFactory factory;

uint32_t id = ReadHeader(myFile);

ClassType* myClass = factory.getClassType(id);
于 2012-10-13T02:28:31.523 回答
0

假设您的 ID 为 0、1、2、3,...,您可以做的是创建一个std::map将每个消息 ID 映射到一个函数指针,该函数指针指向为该 ID 创建正确类型的对象的命名构造函数。

class BaseClass {
   private:
   typedef (BaseClass*) (*NamedConstructor) (SomeType &);

   // The map.
   static std::map<int, NamedConstructor> id_to_constructor;

   public:

   // All the possible message types.
   enum MessageType {
      FooMsg = some_value,
      BarMsg = some_other_value,
      ... // potentially a whole lot more
   };

   // Add a named constructor to the map.
   static void add_handler (MessageType id, NamedConstructor cotr) {
      // Error handling such as duplicates left as an exercise to the user.
      id_to_constructor[id] = cotr;
   }

   // Function that applies the map.
   static void handle_message (int id, SomeType & my_file) {
      // Error handling such as a missing entry left as an exercise to the user.
      NamedConstructor cotr = id_to_constructor[id];
      cotr (my_file);
   }
   ...
};


class Foo : public BaseClass {
   public:
   static BaseClass* create_foo (SomeType & my_file) {
      return new Foo (my_file); // Or use a smart pointer.
   }

   // Member data and member functions elided.
   ...
};

class Bar : public BaseClass {
   public:
   static BaseClass* create_bar (SomeType & my_file) {
      return new Bar (my_file); // Or use a smart pointer.
   }

   // Member data and member functions elided.
    ...
};

您将需要一些机制来使用该方法 注册命名构造函数Foo::create_foo()等。如果您有 500 种消息类型,那就是 500 行代码,但它将是直线(没有 if,没有 switch)代码。Bar::create_bar()BaseClassadd_handler

另一种方法是带有 500 个案例的 switch 语句。叶赫。

那么为什么是地图而不是矢量呢?如果您知道 ID 将变为 0、1、2、...,那么向量就可以了。如果你有差距怎么办?如果设计上有很大的差距怎么办?例如,可以对消息 ID 进行汉明编码,以减少错误。

于 2012-10-13T03:13:09.697 回答
0

基类中的静态函数看起来像:

ClassTypes create(FileType myFile)
{
    // read class ID
    uint32_t id = ReadHeader(myFile);

    // create correct class
    switch (id)
    {
        case ID_CLASS_FOO: myClass = new Foo(myFile); break;
        case ID_CLASS_BAR: myClass = new Bar(myFile); break;
        /* ... */
    }

    return myClass;
}

至少这样,而不是

myClass = new ClassTypes[id](myFile);

你可以做

myClass = ClassTypes::create(myFile);
于 2012-10-13T02:17:51.073 回答