Progettazione C ++ per classi encoder / decodificatore con diversi tipi memorizzati

4

Ho bisogno di gestire diversi elementi in un vettore, ogni elemento possiede un parametro specifico (intero o stringa), così posso facilmente gestire la codifica / decodifica di una serie di elementi.

  • La codifica di un elenco di elementi serializza gli elementi, inserendo un separatore tra loro.

  • La decodifica di una stringa consiste nel trovare i separatori per ciascuna sottostringa, estraendo l'ID e il valore specifico del tipo. Esempio (sapendo che "FOO" è associato a un numero intero e "BAR" è associato a una stringa): "FOO = 54, BAR = ciao" restituisce {{ID_FOO, 54}, {ID_BAR, "ciao"}}.

Ho il seguente design semplice:

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

(Questo non è un esempio di compilazione completo.)

È un progetto logico? Come potrebbe essere ottimizzato in modo che possa corrispondere a qualsiasi modello di progetto esistente? In particolare, mi occupo della tecnica di decodifica, che qui è suddivisa in BaseList :: Decode () e il metodo statico Base :: Decode ().

    
posta Teuxe 22.07.2015 - 17:17
fonte

2 risposte

1

Ok, la vera domanda era su come gestire la differenza tra {FOO, 54} e {BAR, "ciao"}. Questo è il classico caso d'uso di un sindacato:

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

sfortunatamente, i sindacati non gestiscono molto bene i costruttori, quindi std :: string non funzionerà molto bene all'interno di union (a meno che non lo abbiano cambiato di recente :-). Ma c'è un altro modo per implementare un sindacato:

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

I puntatori NULL indicano che il valore non è in uso. (nota che questa classe dell'Unione diventa un incubo da mantenere, se è necessario aggiungere altri tipi alternativi)

Ora la struttura richiesta è la seguente:

struct A { int id; Union u; };

Una volta che hai fatto questo, farne una matrice è molto banale:

std::vector<A> vec;

sarà in grado di memorizzare valori come {{ID_FOO, 33}, {ID_BAR, "ciao"}}

    
risposta data 23.07.2015 - 03:41
fonte
0

Quello che ho finito quando scrivevo la serializzazione era il seguente:

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

E quindi solo le classi derivate implementeranno Create () e 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]; 
   }
};

Purtroppo questo sta diventando troppo lungo per il sito, ma usando queste classi base, è possibile creare ad esempio un parser per le strutture arbitrarie. Vedi questo link per ulteriori dettagli: link

Creerò un'altra risposta per rispondere alla tua domanda effettiva.

    
risposta data 23.07.2015 - 02:14
fonte

Leggi altre domande sui tag