0

I am loading different types of derived classes from a file, which are of the form:

  • 4-byte class ID header
  • n-byte serialized data

Each of the classes are inherited from a same base class, but I have some trouble creating them elegantly. This is my current code (here Foo and Bar are inherited from the same type):

// 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;
    /* ... */
}

But I find this rather ugly, tedious and prone to error, since for every extra class I add, I need one extra define/enum member, and one additional line in the switch.

What I am looking for is something where I would declare a compile-time "type array" like such:

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

And then, when reading the file, just go:

myClass = new ClassTypes[id](myFile);

Is there some way to do this in C++?

3 Answers 3

2

You could create a factory class.

Factory definition:

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;
        }
};

Factory use:

ClassFactory factory;

uint32_t id = ReadHeader(myFile);

ClassType* myClass = factory.getClassType(id);
Sign up to request clarification or add additional context in comments.

Comments

0

What about a static function in your base class that looks something like:

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;
}

At least that way, instead of

myClass = new ClassTypes[id](myFile);

you could do

myClass = ClassTypes::create(myFile);

Comments

0

Assuming your IDs go 0, 1, 2, 3, ..., What you could do is to create a std::map that maps each of the message ID to a function pointer to named constructor that creates the right kind of object for that 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.
    ...
};

You'll need some mechanism to register the named constructors Foo::create_foo(), Bar::create_bar(), etc. using the BaseClass method add_handler. If you have 500 message types, that's 500 lines of code, but it will be straight line (no if, no switch) code.

The alternative is a switch statement with 500 cases. Yech.

So why a map rather than an vector? If you know the IDs will go 0, 1, 2, ..., a vector is fine. What if you have gaps? What if by design you have big huge gaps? For example, the message IDs might be Hamming-encoded so as to reduce errors.

1 Comment

Looks good. Doesn't appear too much different than the example that I posted. Has some extra features, like dynamically adding new child types.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.