c++ serializer with template and polymorphism - c++

(For simplicity's sake, serializer will be called write and deserializer will be called read)
I'm writing a C++ game serializer from scratch with no library allowed.
The main concern i have is to keep the read and write in sync (the read values must be the same as the written ones). So the Packer handles both tasks and is specified with an enum.
What i have
enum PackerType {
WRITE,
READ
}
template <PackerType PType>
class Packer {
char *buffer; // Packer will write here
uint32_t index;
template <typename T>
void Pack(T & value); // Calls appropriate functions depending on PType
}
What i want to be able to do
class ElementToSerialize : ISeriablizable {
virtual void WriteAndRead(Packer & p) {
p.Pack(32);
p.Pack("Hello World");
}
}
Packer<WRITE> wpacker;
Packer<READ> rpacker;
rpacker.buffer = wpacker.buffer;
WriteAndRead(wpacker); // Will write everything in wpacker.buffer
WriteAndRead(rpacker); // Will read wpacker.buffer
So i know this is not possible in C++, but what i'm looking for is an elegant way of dealing with this issue. I'm already aware of type-erasure, but i'm not a fan of the solution.

How about starting with something like this:
class IPacker
{
public:
virtual void Pack(int& value) = 0;
virtual void Pack(float& value) = 0;
}
class ISeriablizable
{
public:
virtual void Serialize(IPacker & p) = 0;
}
class WritePacker : public IPacker
{
char* buffer;
int index = 0;
public:
WritePacker(char* buffer) : buffer(buffer) {}
void Pack(int& value) override { /* write to buffer */ }
void Pack(float& value) override { /* write to buffer */ }
}
class ReadPacker : public IPacker
{
char* buffer;
int index = 0;
public:
ReadPacker(char* buffer) : buffer(buffer) {}
void Pack(int& value) override { /* read from buffer */ }
void Pack(float& value) override { /* read from buffer */ }
}
class ElementToSerialize : public ISeriablizable
{
int x = 32;
std::string y = "Hello world";
void Serialize(IPacker & p) override
{
p.Pack(x);
p.Pack(y);
}
}

If it is not a requirement, you could avoid using templates altogether, and just store whether the Packer is read or write so you know it at runtime (example):
class Packer {
public:
enum Type {
READ,
WRITE
};
char *buffer; // Packer will write here
uint32_t index;
Type type;
Packer(Type t) : type(t) {}
void Pack(int& value);
void Pack(float& value);
void Pack(std::string& value);
// etc...
};

Related

Omitting parameter with default value in a subclass function override

I am writing an interface for several I/O classes.
There is a function that looks for information in different kinds of files (sometimes html, sdb, txt, ...):
bool Search(std::string file, std::string field)
However, one of these requires an additional parameter to complement the SQL query. In this case the sdb needs to specify in what table the field is located.
I am trying something like the following (it does not compile, I am aware):
class fileIO{
public:
virtual ~FileIO(){};
virtual bool Search(std::string file, std::string field,
std::string additional = 0 /* for sdb only */) = 0;
}
class readHTML : fileIO{
public:
bool Search(std::string file, std::string field); //does NOT override virtual method
Is there anything that can give me the behavior I am looking for?
Is such strategy according to C++ standards?
What else could I add to replace such enforcement on the interface?
I am sorry if the title is misleading, I am looking for an alternative with that behavior. I could not find it so far.
You don't need it, I'd say.
At the caller site, there is only two possibilities: you know your specific fileIO instance is a sdbIO or you don't. If you do, you can call an overloaded version of Search defined in sdbIO which takes this additional info. If you don't, you don't and sdbIO::Search should be defined in terms of its overloaded version.
struct fileIO
{
virtual bool Search(std::string file, std::string field) = 0;
}
struct sdbIO : fileIO
{
bool Search(std::string file, std::string field, std::string additional);
bool Search(std::string file, std::string field) override
{
Search(file, field, "");
}
};
At the caller site:
void f(fileIO& io)
{
// I know this is a sdb:
dynamic_cast<sdbIO&>(io).Search("/file", "text", "WHERE answer=42");
// I don't
io.Search("/file", "text");
}
notes: do you really need a copy of those strings?
You can hide the virtual function in the non-public interface and make the public interface (with the default argument) non-virtual.
struct Interface
{
...
// public interface calls the abstract members.
bool Search(string const&a, string const&b, string const&c = "")
{
if(c.empty() && need_third_string())
throw runtime_error("search requires an additional string argument");
return search(a,b,c);
}
protected:
virtual bool need_third_string() const = 0;
virtual bool search(string const&, string const&, string const&) const=0;
};
with obvious derivations:
struct A : Interface
{
protected:
bool need_third_string() const override
{ return false; }
bool search(string const&a, string const&b, string const&) const override
{ /* search ignoring third argument */ }
};
struct B : Interface
{
protected:
bool need_third_string() const override
{ return true; }
bool search(string const&a, string const&b, string const&c) const override
{ /* search ignoring using all 3 arguments */ }
};
I don't see any problem with above two way to handle things. Still, I have just one more.
#include<bits/stdc++.h>
#include <stdexcept>
using namespace std;
typedef struct
{
std::string arg1;
std::string arg2;
std::string arg3;
} Param;
class FileIO{
public:
virtual ~FileIO(){};
virtual void Search(Param param) = 0;
};
class ReadHTML : public FileIO{
public:
void Search(Param param)
{
if(param.arg3.length() > 0) // Some logic to handle things here.
search3(param.arg1, param.arg2, param.arg3);
else
throw std::runtime_error("Bad call with param");
}
private:
void search3(std::string arg1, std::string arg2, std::string arg3)
{
std::cout << " I am called with Html::Search3" << std::endl;
}
};
class ReadTxt : public FileIO{
public:
void Search(Param param)
{
if(param.arg1.length() && param.arg2.length()) // Some logic to handle things here.
search2(param.arg1, param.arg2);
else
throw std::runtime_error("Bad call with param");
}
private:
void search2(std::string arg1, std::string arg2)
{
std::cout << " I am called with Txt::Search2" << std::endl;
}
};
// Driver program to test above function
int main()
{
FileIO *io = new ReadHTML();
Param paramHtml = {"a", "b", "c"};
io->Search(paramHtml); // Put some try .. catch
Param paramTxt = {"a", "b"};
io = new ReadTxt(); // Put some try...catch
io->Search(paramTxt);
return 0;
}

identifying the design pattern name used

I want to check if the examples below (from a test interview) corresponds to the correct design pattern name :
Example 1 : can that piece of code illustrate the "Builder" pattern or it might be the "Strategy" one ?
FileStream* files = new FileStream("my_file.zip");
BufferedStream* bufferds = new BufferedStream(files);
ZipStream* zips = new ZipStream(bufferds);
Example 2 : is the code below represent the "Strategy" pattern ?
struct UnixText {
void write(string str) { cout << str; }
void lf() { cout << "\n"; }
};
struct WindowsText {
void write(string str) { cout << str; }
void crlf() { cout << "\r\n"; }
};
struct Writer {
virtual void write(string str) = 0;
virtual void newline() = 0;
virtual ~Writer() {}
};
struct UnixWriter : Writer {
UnixWriter(UnixText* tx) { _target = tx; }
virtual void write(string str) { _target->write(str); }
virtual void newline() { _target->lf(); }
private:
UnixText* _target;
};
struct WindowsWriter : Writer {
WindowsWriter(WindowsText* tx) { _target = tx; }
virtual void write(string str) { _target->write(str); }
virtual void newline() { _target->crlf(); }
private:
WindowsText* _target;
};
int main()
{
Writer* writer = (g_IsUnix) ? (Writer*) new UnixWriter(new UnixText()) : (Writer*) new WindowsWriter(new WindowsText());
writer->write("Hello");
writer->newline();
writer->write("World");
}
The first example uses I/O streams and it is a good use of Decorator pattern. Here it has a constructor that takes an instance of the same abstract class or interface. That's the recognition key of the Decorator pattern
The second one, you are passing some Writing Strategy to the UnixWriter and WindowsWriter which is the context. So it can be considered as Strategy pattern. But you can still improve it by having a contract for Writing Strategy. So your concrete writers should only know about that super type rather than having references to concrete implementations. That will make your system more flexible.

Any way of creating an array/vector which contains references to different types of variables?

I've been banging my head for quite some time now trying to create an array/vector that can contain references to several types of variables, see the example:
class Validate
{
private:
some_array/vector[]; //0 would refer to x, 1 to y, and so on..
uint8_t x;
uint16_t y;
int32_t z;
public:
Validate();
void doSomething(uint8_t &member);
void doSomething(uint16_t &member);
void doSomething(int32_t &member);
}
The whole point is so that I can use this array/vector easily in a "for loop", something like this:
void Validate::doSomething(uint_8 &member)
{
//Do whatever with the variable refered to.
}
Validate::Validate()
{
for(int i = 0 ; i < 2 ; i++)
doSomething(some_array/vector[i]);
}
Perhaps somebody have an answer or possibly a better solution for me.
You have two problems. Firstly, a container cannot contain elements of different types. Secondly, you cannot store references in a container.
One way of solving the first problem is to use std::variant from C++17 (or boost::variant). To solve the second problem you could use std::reference_wrapper:
class Validate {
using Uint8Ref = std::reference_wrapper<uint8_t>;
using Uint16Ref = std::reference_wrapper<uint16_t>;
using Uint32Ref = std::reference_wrapper<int32_t>;
using MemberType = std::variant<Uint8Ref, Uint16Ref, Uint32Ref>;
std::vector<MemberType> members;
uint8_t x;
uint16_t y;
int32_t z;
public:
Validate();
void doSomething(uint8_t &member);
void doSomething(uint16_t &member);
void doSomething(int32_t &member);
};
Validate::Validate() : members({std::ref(x), std::ref(y), std::ref(z)}) {
for (auto member : members) {
std::visit([this](auto member){this->doSomething(member);}, member);
}
}
Live demo.
Alternatively, you could create a polymophic base class for a member that you can store in a container:
class MemberType {
public:
virtual ~MemberType(){}
virtual void accept(Validate& validate) = 0;
};
class Validate {
std::array<std::unique_ptr<MemberType>, 3> members;
// as before...
};
template<typename T>
class MemberTypeImpl : public MemberType {
T& member;
public:
MemberTypeImpl(T& member) : member(member){}
void accept(Validate& validate) override {
validate.doSomething(member);
}
};
template<typename T>
std::unique_ptr<MemberType> make_member_type(T& member) {
return std::make_unique<MemberTypeImpl<T>>(member);
}
Validate::Validate()
: members({make_member_type(x), make_member_type(y), make_member_type(z)}) {
for (auto& member : members) {
member->accept(*this);
}
}

Classes not registering into factory

I'm trying to create an easy way to register all my Project Euler solutions into a std::map in a factory pattern, in order to be able to refer to them in code by number. I found a fantastic answer on this site (Dynamically register constructor methods in an AbstractFactory at compile time using C++ templates) and came up with this solution:
EulerProblem.h:
#ifndef EULERPROBLEM_H
#define EULERPROBLEM_H
#include<string>
#include<sstream>
#include<QObject>
#include<QString>
// BASE PROBLEM CLASS
class EulerProblem : public QObject
{
Q_OBJECT
signals:
void printSignal(QString str);
void debugSignal(QString str);
void problemTextSignal(QString str);
protected:
EulerProblem() {}
void print(QString str);
void debug(QString str);
void setProblemText(QString str);
protected:
int problemNumber;
QString problemText;
public:
virtual ~EulerProblem() { }
void initialize();
virtual void doProblem() = 0;
};
// PROBLEM TEMPLATE, DERIVE PROBLEMS FROM THIS
template<int NUM, typename IMPL>
class ProblemTmpl : public EulerProblem
{
enum { _PROBLEM_ID = NUM };
public:
static EulerProblem* Create() { return new IMPL(); }
static const uint16_t PROBLEM_ID; // for registration
// static void Enable() { volatile uint16_t x = PROBLEM_ID; }
protected:
ProblemTmpl() { problemNumber = PROBLEM_ID; } //use parameter to instantiate template
};
// PROBLEM FACTORY, USE THIS TO GET PROBLEMS
class ProblemFactory
{
public:
typedef EulerProblem* (*t_pfFactory)();
static ProblemFactory *getInstance()
{
static ProblemFactory fact;
return &fact;
}
uint16_t Register(uint16_t msgid, t_pfFactory factoryMethod)
{
printf("Registering constructor for msg id %d\n", msgid);
m_List[msgid] = factoryMethod;
return msgid;
}
EulerProblem *Create(uint16_t msgid)
{
return m_List[msgid]();
}
std::map<uint16_t, t_pfFactory> m_List;
private:
ProblemFactory() {};
ProblemFactory(ProblemFactory const&) {};
ProblemFactory& operator=(ProblemFactory const&);
~ProblemFactory() {};
};
#endif // EULERPROBLEM_H
EulerProblem.cpp (note the first line, which is intended to automatically call Register()):
#include "eulerproblem.h"
template <int TYPE, typename IMPL>
const uint16_t ProblemTmpl<TYPE, IMPL>::PROBLEM_ID =
ProblemFactory::getInstance()->Register(ProblemTmpl<TYPE, IMPL>::_PROBLEM_ID, &ProblemTmpl<TYPE, IMPL>::Create);
void EulerProblem::initialize()
{
setProblemText(problemText);
}
void EulerProblem::debug(QString str)
{
emit debugSignal(str);
}
void EulerProblem::print(QString str)
{
emit printSignal(str);
}
void EulerProblem::setProblemText(QString str)
{
emit problemTextSignal(str);
}
Example problem class (049.h):
#ifndef _49_h
#define _49_h
class Problem049 : public ProblemTmpl<49, Problem049>
{
public:
Problem049()
{
problemText =
"The arithmetic sequence, 1487, 4817, 8147, in which each of the terms increases by 3330, is unusual in two ways: (i) each of the three terms are prime, and, (ii) each of the 4-digit numbers are permutations of one another.\n"
"\n"
"There are no arithmetic sequences made up of three 1-, 2-, or 3-digit primes, exhibiting this property, but there is one other 4-digit increasing sequence.\n"
"\n"
"What 12-digit number do you form by concatenating the three terms in this sequence?";
}
void doProblem()
{
// problem solution here
}
};
#endif /* _49_h */
So when I use the following code (the connect() calls are Qt stuff for wiring up signals):
ep = ProblemFactory::getInstance()->Create(49);
connect(ep, SIGNAL(printSignal(QString)), this, SLOT(addOutput(QString)));
connect(ep, SIGNAL(debugSignal(QString)), this, SLOT(addDebug(QString)));
connect(ep, SIGNAL(problemTextSignal(QString)), this, SLOT(setProblem(QString)));
ep->initialize();
I get a segfault from ProblemFactory::Create() because the std::map is empty--Register() was never called. The code compiles fine, however. Can anyone see what I'm doing wrong here? I've been hunting for a while.
I ran and compiled the example given as an answer in the other question, and it works fine. It's not something conflicting with Qt, is it?
Figured it out, though I don't understand why the solution worked.
I moved the following line:
template <int TYPE, typename IMPL>
const uint16_t ProblemTmpl<TYPE, IMPL>::PROBLEM_ID =
ProblemFactory::getInstance()->Register(ProblemTmpl<TYPE, IMPL>::_PROBLEM_ID, &ProblemTmpl<TYPE, IMPL>::Create);
From the top of EulerProblem.cpp to the bottom of EulerProblem.h and it worked. Does anyone have any insight as to why that is?

How can I make my classes more platform-independent?

I have a problem. I've written a GPS module that can detect the type of the message on the fly and configure them if needed. I've done it by composition of several classes. To make code a little more independent from the platform (stm32) I created a IStreamDevice interface that has baic i/o operations. It works. Everything appers to be great, but the classs are apparently coupled. That't why I have several question:
How can I avoid the passing IStreamDevice to all devices?
How can I make the whole design more platform-independent (and os-independent)? We have plans to move to another OS in the nearest future. It is POSIX compliant. I think I will be able to implement my IStreamDevice interface there (the buses I can aend up using are UART and SPI. In my current version I use only UART). Am I wrong?
class IStreamDevice
{
public:
virtual ~IStreamDevice() {}
virtual uint32_t read(uint8_t* data, uint32_t size) = 0;
virtual uint32_t write(const uint8_t* data, uint32_t size) = 0;
virtual uint32_t bytesToRead() const = 0;
virtual uint32_t bytesToWrite() const = 0;
};
class GPSModule {
public:
GPSModule(periph::IStreamDevice *source);
~GPSModule();
void turnDevice1Messages();
void turnDevice2Messages();
void configureDevice1(...);
void configureDevice2(...);
void Scan();
private:
Device1Configurator *_device1_configurator;
Device2Configurator *_device2_configurator;
StreamDeviceScanner*_scanner;
periph::IStreamDevice *_source;
};
GPSModule::GPSModule(periph::IStreamDevice *source): _source(source)
{
_scanner= new StreamDeviceScanner(_source);
_device1_configurator = new Device1Configurator(_source);
_device2_configurator = new Device2Configurator(_source);
}
GPSModule::~GPSModule()
{
delete _scanner;
}
void GPSModule::Scan()
{
_scanner->Scan();
}
void GPSModule::turnDevice1Messages() {
_device1_configurator->turnMessages();
}
class StreamDeviceScanner{
public:
StreamDeviceScanner(periph::IStreamDevice *source);
~StreamDeviceScanner();
void Scan();
private:
typedef enum
{
WAITING_SYNC,
WAITING_DEVICE1_MSG,
WAITING_DEVICE2_MSG
} states_t;
periph::IStreamDevice *_source;
ProtocolScanner *_protocol_scanner;
states_t _state;
private:
states_t _catchSync();
uint32_t _read(uint8_t* data, uint32_t length) { return _source->read(data,length); }
uint32_t _bytesToRead() const { return _source->bytesToRead(); }
};
StreamDeviceScanner::StreamDeviceScanner(periph::IStreamDevice *source):
_source(source),
_state(WAITING_SYNC)
{
_protocol_scanner = new ProtocolScanner(source);
}
StreamDeviceScanner::~StreamDeviceScanner()
{
delete _protocol_scanner;
}
void StreamDeviceScanner::Scan()
{
while (_source->bytesToRead()) {
switch (_state)
{
case WAITING_SYNC:
_state = _catchSync();
break;
case WAITING_DEVICE1_MSG:
_protocol_scanner->Device1Scan()
_state = WAITING_SYNC;
break;
case WAITING_DEVICE2_MSG:
_protocol_scanner->Device2Scan()
_state = WAITING_SYNC;
break;
}
}
}
class ProtocolScanner {
private:
Device1Scanner *_Device1Scanner;
Device2Scanner *_Device2Scanner;
public:
ProtocolScanner(periph::IStreamDevice *source)
{
_Device1Scanner = new Device1Scanner(source);
_Device2Scanner = new Device2Scanner(source);
}
~ProtocolScanner()
{
delete _Device1Scanner;
delete _Device1Scanner;
}
bool Device1Scan() const { return _Device1Scanner->Scan(); }
bool Device2Scan() const { return _Device2Scanner->Scan(); }
};
class Device1Scanner {
public:
Device1Scanner(periph::IStreamDevice *source);
~Device1Scanner();
bool Scan();
private:
enum { BUFFER_LENGTH = 8192 };
typedef enum {
Waiting_Header,
Waiting_Payload,
Waiting_Checksum
} state_t;
uint8_t _buffer[BUFFER_LENGTH];
periph::IStreamDevice *_source;
state_t _state;
Device1Parser *_destination;
Device1Scanner::NovatelMessage _message;
private:
uint32_t _read(uint8_t* data, uint32_t size) { return _source->read(data,size); }
const uint32_t _bytesToRead() const { return _source->bytesToRead(); }
bool _receiveHeader();
bool _receivePayload();
bool _receiveChecksum();
bool _validChecksum() const;
};
Device2Scanner looks exactly the same. I'd like to hear everything that anyone has to say about the design.
I don't see any inherent problem with your design. Your IStreamWriter interface seems like a proper abstraction of the underlying bus, without being dependent on specific bus details. That complies with the Dependency Inversion principle and with design-by-contract approach. I also don't see tight coupling in your classes. You're accessing the bus via its handler, according to the interface specification, without dependency on the implementation of the actual bus handling class.
There is nothing platform dependent in the shown code. If the bus handling differs per platform, there is not much you can do except providing a different implementations for IStreamWriter according to platform.