На самом деле это довольно простой вопрос (как вы можете себе представить, вы определенно не единственный, кто десериализует в C ++).
То, что вы ищете, называется виртуальным строительством.
C ++ не определяет Virtual Construction, но его легко аппроксимировать, используя шаблон проектирования Prototype
или метод Factory
.
Я лично предпочитаю подход Factory
, потому что Prototype
означает, что у него есть некоторый экземпляр по умолчанию, который реплицируется и затем определяется ... проблема в том, что не у всех классов есть значимое значение по умолчанию, и в этом отношении значимое Default Constructor
.
Подход Factory
достаточно прост.
- Вам нужен общий базовый класс для сообщений, а другой - для анализаторов
- Каждое сообщение имеет как тег, так и связанный с ним анализатор
Давайте посмотрим код:
// Framework
class Message
{
public:
virtual ~Message();
};
class Parser
{
public:
virtual ~Parser();
virtual std::auto_ptr<Message> parse(std::istream& serialized) const;
};
// Factory of Messages
class MessageFactory
{
public:
void register(std::string const& tag, Parser const& parser);
std::auto_ptr<Message> build(std::string const& tag, std::istream& serialized) const;
private:
std::map<std::string,Parser const*> m_parsers;
};
И с этой структурой (по общему признанию простой), некоторые производные классы:
class MessageA: public Message
{
public:
MessageA(int a, int b);
};
class ParserA: public Parser
{
public:
typedef std::auto_ptr<MessageA> result_type;
virtual result_type parse(std::istream& serialized) const
{
int a = 0, b = 0;
char space = 0;
std::istream >> a >> space >> b;
// Need some error control there
return result_type(new MessageA(a,b));
}
};
И наконец, использование:
int main(int argc, char* argv[])
{
// Register the parsers
MessageFactory factory;
factory.register("A", ParserA());
// take a file
// which contains 'A 1 2\n'
std::ifstream file = std::ifstream("file.txt");
std::string tag;
file >> tag;
std::auto_ptr<Message> message = factory.parse(tag, file);
// message now points to an instance of MessageA built by MessageA(1,2)
}
Это работает, я знаю, потому что я использую его (или вариант).
Есть несколько вещей, которые следует учитывать:
- Возможно, вы захотите сделать
MessageFactory
синглтоном, тогда это позволит вызывать его при загрузке библиотеки, и, таким образом, вы сможете зарегистрировать ваши парсеры, создавая статические переменные. Это очень удобно, если вы не хотите, чтобы main
регистрировал каждый тип парсера: locality> меньше зависимостей.
- Теги должны быть общими. Также нет ничего необычного в том, что тег обслуживается виртуальным методом класса Message (называемым тегом).
Как:
class Message
{
public:
virtual ~Message();
virtual const std::string& tag() const = 0;
virtual void serialize(std::ostream& out) const;
};
- Логика для сериализации также должна быть общей, для объекта не является чем-то необычным обрабатывать свою собственную сериализацию / десериализацию
Как:
class MessageA: public Message
{
public:
static const std::string& Tag();
virtual const std::string& tag() const;
virtual void serialize(std::ostream& out) const;
MessageA(std::istream& in);
};
template <class M>
class ParserTemplate: public Parser // not really a parser now...
{
public:
virtual std::auto_ptr<M> parse(std::istream& in) const
{
return std::auto_ptr<M>(new M(in));
}
};
Что хорошо с шаблонами, так это то, что они не перестают меня удивлять
class MessageFactory
{
public:
template <class M>
void register()
{
m_parsers[M::Tag()] = new ParserTemplate<M>();
}
};
//skipping to registration
factory.register<MessageA>();
Теперь разве не красиво :)?