C++ design for encoder/decoder classes with different stored types

cdesign-patternsinheritance

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().

Best Answer

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