4

I need to handle different elements in a vector, each element owning a specific parameter (integer or string), so that I can easily handle encoding/decoding of a series of elements.

  • Encoding a list of elements serializes the elements, inserting a separator in-between each.

  • Decoding a string consists in finding the separators then for each substring, extracting the ID and type-specific value. Example (knowing that "FOO" is associated to an integer and "BAR" is associated to a string): "FOO=54,BAR=hello" gives {{ID_FOO, 54}, {ID_BAR, "hello"}} .

I have the current following bare design:

#include <cstdint>
#include <cstdlib>
#include <string>
#include <memory>

enum eID { /*...*/ };

// Base class
class Base {
private:
    enum eID m_Id;
public:
    Base(enum eID = 0): m_Id(eID) {}
    virtual std::string Encode() {
        // Return encoding of m_Id
    }
    static Base* Decode(const std::string &str) {
        Base *ptr;
        // Extract id
        // According to the kind of id, create a new Derived<T>
        // giving the substring to the constructor
        // ptr = new Derived<...>(id, str.substr(...));
        return ptr;
    }
};

template <class T>
class Derived<T> : public Base {
private:
    T m_Value;
public:
    Derived(enum eID id, const std::string &str); // participates to decoding
    std::string Encode();
};

template<>
Derived<std::string>::Derived(enum eID id, const std::string &str) {
    m_Value = str; // easy
}

template<>
std::string Derived<std::string>::Encode() {
    return static_cast<Base*>(this)->Encode() + m_Value;
}

template<>
Derived<long>::Derived(enum eID id, const std::string &str) {
    m_Value = strtol(str.c_str(), nullptr, 10);
}

template<>
std::string Derived<long>::Encode() {
    // Encode id and integer param to a string
}

class BaseList {
private:
    std::vector<std::unique_ptr<Base>> m_List;
public:
    void Append(Base *ptr) {
        m_List.push_back(std::unique_ptr<Base>(ptr));
    }
    std::string Encode() {
        // Call each Encode() function and concatenate strings together
        // Insert a delimiter in-between substrings
    }
    void Decode(const std::string &sToDecode) {
        // Look for delimiters in sToDecode
        // For each delimited substring:
        // - call Base::Decode() to allocate a new type-specific object
        // - push back into m_List
    }
}

(This is not be a complete compiling example.)

Is it a logic design? How could it be optimized so that it may match any existing design pattern(s)? In particular I am concerned with the decoding technique, which here is split into BaseList::Decode() and the static method Base::Decode().

  • If there's no hard requirement on the specific serialized format I think I'd start with boost::serialize – jthill Jul 22 '15 at 16:53
  • Actually the "serialized" format is part of a predefined communication protocol, so that cannot be customized; but internal representation of data (id and type-specific value of each element whose order must be kept in vector) can be upgraded. – Teuxe Jul 22 '15 at 17:04
  • 1
    You need a factory (pattern) for decoding elements. – Dieter Lücking Jul 22 '15 at 17:10
  • As far as I understand, my Base::Decode() static function appears as factory pattern. – Teuxe Jul 23 '15 at 14:57
1

Ok, the actual question was about how to handle the difference between {FOO, 54} and {BAR, "hello"}. This is classic use case of an union:

struct A { int id; union { long val; std::string s; } un; };

unfortunately, unions don't handle constructors very well, so std::string wont work very well inside union (unless they changed it recently :-). But there's another way to implement an union:

class Union {
public:
  Union() : m_s(0), m_val(0) {}
  Union(std::string s) : m_s(new string(s)), m_val(0) { }
  Union(long val) : m_s(0), m_val(new long(val)) { }
  ~Union() { delete m_s; delete m_val; }
  Union(const Union &u) 
  : m_s(u.m_s ? new string(*u.m_s) : 0), 
    m_val(u.m_val ? new long(*u.m_val) : 0) { }
  void operator=(const Union &u) { 
        delete m_s; m_s=0;
        delete m_val; m_val=0;
        if (u.m_s) m_s = new std::string(*u.m_s); 
        if (u.m_val) m_val = new long(*u.m_val);
        }
 private:
  std::string *m_s; 
  long *m_val;
 };

NULL pointers are indicating that the value is not being used. (note that this Union class becomes a nightmare to maintain, if you need to add more alternative types to it)

Now the required struct looks like this:

struct A { int id; Union u; };

Once you have this, making an array out of it is very trivial:

std::vector<A> vec;

will be able to store values like {{ID_FOO,33}, {ID_BAR,"hello"}}

0

What I ended up when writing serialization was the following:

class ParserPairBase 
{
public:
//virtual std::type_info TypeInfo() const=0;
virtual size_t SizeOf() const=0;
struct PtrSize
{
  char *ptr;
  size_t size;
};
virtual PtrSize BaseParse(std::string s, bool &success) const=0;
virtual std::string BaseCreate(PtrSize psz) const=0;
};

template<class T>
class ParserPair : public ParserPairBase 
{
public:
   virtual std::string Create(T t) const=0;
   virtual T Parse(std::string s, bool &success) const=0;
   //std::type_info TypeInfo() const { return typeid(T); }
   virtual size_t SizeOf() const { return sizeof(T); }
   virtual PtrSize BaseParse(std::string s, bool &success) const
      {
      PtrSize sz;
      T *ptr = new T;
      sz.ptr = (char*)ptr;
      sz.size = sizeof(T);
      *ptr = Parse(s, success);
      return sz;
      }
    virtual std::string BaseCreate(PtrSize psz) const
       {
       T *ptr = (T*)psz.ptr;
       std::string s = Create(*ptr);
       return s;
       }
   };

And then just derived classes will implement Create() and Parse():

class CharParser : public ParserPair<char>
{
public: 
   std::string Create(char c) const { std::string s; s+=c; return s; }
   char Parse(std::string s, bool &success) const { 
      if (s.length()!=1) { success = false; return ' '; } 
      success = true; 
      return s[0]; 
   }
};

Unfortunately, this is getting too long for the site, but using these base classes, it's possible to create for example a parser for arbitrary structs. See this link for further details: http://sourceforge.net/p/gameapi/code/ci/master/tree/GameApi/Parser.hh

I'll create another answer for answering your actual question.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.