Inspired by this question I decided to write an alternative that uses polymorhphism and a Factory pattern. The code works for the subset of implemented types, namely BYTE
, WORD
and STRING
but I have a few concerns:
- I'd like to get rid of the big ugly switch in the
TLVFactory
. - Is there a way to make the
TLVFactory::create
a static member ofTLVObject
? - Any other code improvement suggests, perhaps regarding casts.
tlv2.cpp
#include <iostream>
#include <iomanip>
#include <fstream>
#include <cstdint>
#include <string>
#include <vector>
#include <memory>
#include <stdexcept>
class TLVObject
{
public:
enum TLV_TYPE {UNDEFINED = 0, BYTE, WORD, DWORD, QWORD, STRING, BLOB};
TLVObject(TLV_TYPE type) :
m_type{type}
{}
virtual ~TLVObject() {}
virtual size_t len() const { return 0; }
virtual std::ostream &write(std::ostream &out) const {
writeTag(out);
writeLen(out);
writeData(out);
return out;
}
virtual std::ostream &writeTag(std::ostream &out) const {
return out.put(m_type);
}
virtual std::ostream &writeLen(std::ostream &out) const {
return out.put(len());
}
virtual std::ostream &writeData(std::ostream &out) const {
return out;
}
virtual std::istream &read(std::istream &in) {
readTag(in);
readLen(in);
readData(in);
return in;
}
virtual std::istream &readTag(std::istream &in) {
char ch;
in.get(ch);
m_type = static_cast<TLV_TYPE>(ch);
return in;
}
virtual std::istream &readLen(std::istream &in) { return in; }
virtual std::istream &readData(std::istream &in) { return in; }
private:
TLV_TYPE m_type;
};
class TLVByte : public TLVObject
{
public:
TLVByte(uint8_t value) :
TLVObject{BYTE},
m_value{value}
{}
TLVByte() : TLVByte{0} {}
virtual ~TLVByte() {}
virtual size_t len() const { return sizeof(m_value); }
virtual std::ostream &writeData(std::ostream &out) const {
return out.put(m_value);
}
virtual std::istream &readLen(std::istream &in) {
if (len() != static_cast<size_t>(in.get())) {
std::out_of_range("TLV len error");
}
return in;
}
virtual std::istream &readData(std::istream &in) {
m_value = in.get(); return in;
}
private:
uint8_t m_value;
};
class TLVWord : public TLVObject
{
public:
TLVWord(uint16_t value) :
TLVObject{WORD},
m_value{value}
{}
TLVWord() : TLVWord{0} {}
virtual ~TLVWord() {}
virtual size_t len() const { return sizeof(m_value); }
virtual std::ostream &writeData(std::ostream &out) const {
return out.put(m_value >> 8).put(m_value & 0xff);
}
virtual std::istream &readLen(std::istream &in) {
if (len() != static_cast<size_t>(in.get())) {
std::out_of_range("TLV len error");
}
return in;
}
virtual std::istream &readData(std::istream &in) {
m_value = in.get() << 8;
m_value |= in.get();
return in;
}
private:
uint16_t m_value;
};
class TLVString : public TLVObject
{
public:
TLVString(std::string str) :
TLVObject{STRING},
m_value{str}
{}
TLVString() : TLVString("") {}
virtual ~TLVString() {}
virtual size_t len() const { return m_value.size(); }
virtual std::ostream &writeData(std::ostream &out) const {
return out << m_value;
}
virtual std::istream &readLen(std::istream &in) {
m_value.reserve(in.get());
return in;
}
virtual std::istream &readData(std::istream &in) {
for (int i = m_value.capacity(); i; --i) {
m_value += in.get();
}
return in;
}
private:
std::string m_value;
};
class TLVFactory
{
public:
static std::unique_ptr<TLVObject> create(TLVObject::TLV_TYPE type) {
switch (type) {
case TLVObject::BYTE:
return std::make_unique<TLVByte>();
break;
case TLVObject::WORD:
return std::make_unique<TLVWord>();
break;
case TLVObject::STRING:
return std::make_unique<TLVString>();
break;
case TLVObject::DWORD:
case TLVObject::QWORD:
case TLVObject::BLOB:
default:
std::out_of_range("Can't construct object of that type");
}
return std::make_unique<TLVObject>(TLVObject::UNDEFINED);
}
};
class TLVCollection : public std::vector<std::unique_ptr<TLVObject>> {
public:
void write(std::ostream &out) const {
for (const auto &item : *this) {
item->write(out);
}
}
void read(std::istream &in) {
while (in.good() && in.peek() > 0) {
push_back(TLVFactory::create(static_cast<TLVObject::TLV_TYPE>(in.peek())));
back()->read(in);
}
}
};
Simple test code
int main()
{
TLVCollection vec;
vec.push_back(std::make_unique<TLVByte>(TLVByte{7}));
vec.push_back(std::make_unique<TLVString>(TLVString{"banana"}));
vec.push_back(std::make_unique<TLVWord>(TLVWord{0x1234}));
std::ofstream out("tlvtest.bin");
vec.write(out);
out.close();
TLVCollection v2;
std::ifstream in("tlvtest.bin");
v2.read(in);
in.close();
v2.write(std::cout);
}
Results
When I run the program and pipe its output through xxd
which simply converts the binary input to a hexadecimal representation, I get this:
./tlv2 |xxd 00000000: 0101 0705 0662 616e 616e 6102 0212 34 .....banana...4