C++ Custom Binary Resource File - c++

I have spent countless hours searching for information about a topic like this. I am writing my own custom game engine for fun using SDL in C++. I'm trying to create a custom binary file which will manage my in game resources. So far I've not been able to get vectors to play nice when it comes to storing each 'type' of object I place in the file. So I dropped the idea of using vectors and went to arrays. I have both examples below where I use both a vector or an array. So, first I create a header for the file. Here is the struct:
struct Header
{
const char* name; // Name of Header file
float version; // Resource version number
int numberOfObjects;
int headerSize; // The size of the header
};
Then after creating the header, I have another struct which defines how an object is stored in memory. Here it is:
struct ObjectData{
int id;
int size;
const char* name;
// std::vector<char> data; // Does not work very well
// unsigned char* data; // Also did not
// Also does not work, because I do not know the size yet until I have the data.
// char data[]
};
The major issue with this struct is that the vector does not play well, an unsigned char pointer kept giving me issues, and an array of char data (for hexadecimal storage) was not working because my compiler does not like variable arrays.
The final struct is my resource file structure.
struct ResourceFile
{
Header header;
int objectCount;
// Again, vectors giving me issues because of how they are constructed internally
// std::vector<ObjectData> objectList;
// Below does not work because, again, no variable data types;
// ObjectData objects[header.numberOfObjects]
};
My goal is to be able to write out a single struct to a binary file. Like so:
Header header;
header.name = "Resources.bin";
header.version = 1.0f;
header.headerSize = sizeof(header);
//vector<char> Object1 = ByteReader::LoadFile("D:\\TEST_FOLDER\\test.obj");
//vector<char> Object2 = ByteReader::LoadFile("D:\\TEST_FOLDER\\test.obj");
ObjectData cube;
cube.id = 0;
cube.name = "Evil Cubie";
cube.data = ByteReader::LoadFile("D:\\TEST_FOLDER\\test.obj");
cube.size = sizeof(cube.id) + sizeof(cube.name) + cube.data.size();
ofstream resourceFile("D:\\TEST_FOLDER\\Resources.bin", ios::out|ios::app|ios::binary);
resourceFile << header.name << header.version << header.headerSize;;
resourceFile << cube.id << cube.name << cube.size;
for each (char ch in cube.data)
{
resourceFile << ch;
}
resourceFile.close();
/*
ObjectData cube2;
cube.id = 1;
cube.name = "Ugle Cubie";
for each (char ch in Object1)
{
cube.object.push_back(ch);
}
*/
//resourceFile.data.push_back(cube);
//resourceFile.data.push_back(cube2);
//resourceFile.header.numberOfObjects = resourceFile.data.size();
//FILE* dat = fopen(filename, "wb");
//fwrite(&resourceFile, sizeof(resourceFile), 1, dat); // <-- write to resource file
//fclose(dat);
As you noticed above, I tried two different ways. The first way I tried it was using good old fwrite. The second way was not even writing it in binary even though I told the computer to do so through the flags accepted by ofstream.
My goal was to get the code to work fluently like this:
ResourceFile resourceFile;
resourceFile.header.name = "Resources.bin";
resourceFile.header.version = 1;
resrouceFile.header.numberOfObjects = 2;
resourceFile.header.headerSize = sizeof(resourceFile.header);
ObjectData cube;
ObjectData cube2;
resourceFile.data.push_back(cube);
resourceFile.data.push_back(cube2);
resourceFile.header.numberOfObjects = resourceFile.data.size();
FILE* dat = fopen(filename, "wb");
fwrite(&resourceFile, sizeof(resourceFile), 1, dat); // <-- write to resource file
fclose(dat);
Still no cigar. Any one have any pointers (no pun intended) or a proper example of a resource manager?

This is one of the things I specialize in, so here you go. There is a whole school of programming around this, but the basic rules I follow are:
1) Use FIXED-LENGTH structures for things with a "constant" layout.
These are things like the flag bits of the file, bytes indicating the # of sub-records, etc. Put as much of the file contents into these structures as you can- they are very efficient especially when combined with a good I/O system.
You do this using the pre-processor macro "#pragma pack(1)" to align a struct to byte boundaries:
#ifdef WINDOWS
#pragma pack(push)
#endif
#pragma pack(1)
struct FixedSizeHeader {
uint32 FLAG_BYTES[1]; // All Members are pointers for a reason
char NAME[20];
};
#ifdef WINDOWS
#pragma pack(pop)
#endif
#ifdef LINUX
#pragma pack()
#endif
2) Create a base class, pure interface with a name like "Serializable". He is your high-level API for staging entire file objects into and out of raw memory.
class Serializable { // Yes, the name comes from Java. The idea, however, predates it
public:
// Choose your buffer type- char[], std::string, custom
virtual bool WriteToBinary(char* buffer) const = 0;
};
NOTE: To support a static "Load" you will need all your "Serializable"s to have an additional static function. There are several (very different) ways to support that, none of which the language alone will enforce since C++ doesn't have "virtual static".
3) Create your aggregate classes for managing each file type. They should have the same name as the file type. Depending on file structure, each may in turn contain more "aggregator" classes before you get down to the fixed structures.
Here's an example:
class GameResourceFile : public Serializable
{
private:
// Operator= and the copy ctor should point to the same data for files,
// since that is what you get with FILE*
protected:
// Actual member variables- allows specialized (derived) file types direct access
FixedSizeHeader* hdr; // You don't have to use pointers here
ContentManager* innards; // Another aggregator- implements "Serializable"
GameResourceFile(FixedSizeHeader* hdr, ContentManager* innards)
: hdr(hdr), innards(innards) {}
virtual ~GameResourceFile() { delete hdr; delete innards; }
public:
virtual bool WriteToBinary(char* outBuffer) const
{
// For fixed portions, use this
memcpy(outBuffer, hdr, sizeof(FixedSizeHeader)); // This is why we 'pack'
outBuffer += sizeof(FixedSizeHeader); // Improve safety...
return innards->WriteToBinary(outBuffer);
}
// C++ doesn't enforce this, but you can via convention
static GameResourceFile* Load(const char* filename)
{
// Load file into a buffer- You'll want your own code here
// Now that's done, we have a buffer
char* srcContents;
FixedSizeHeader* hdr = new FixedSizeHeader();
memcpy(hdr, srcContents, sizeof(FixedSizeHeader));
srcContents += sizeof(FixedSizeHeader);
ContentManager* innards = ContentManager::Load( srcContents); // NOT the file
if(!innards) {
return 0;
}
return new GameResourceFile(hdr, innards);
}
};
Notice how this works- each piece is responsible for serializing itself into the buffer, until we get to "primitive" structures that we can add via memcpy() (you can make ALL the components 'Serializable' classes). If any piece fails to add, the call returns "false" and you can abort.
I STRONGLY recommend using a pattern like "referenced object" to avoid the memory management issues. However, even if you don't you now provide users a nice, one-stop shopping method to load data objects from files:
GameResourceFile* resource = GameResourceFile::Load("myfile.game");
if(!resource) { // Houston, we have a problem
return -1;
}
The best thing yet is to add all low-level manipulation and retrieval APIs for that kind of data to "GameResourceFile". Then any low-level state machine coordination for committing changes to disk & such is all localized to 1 object.

Related

const correctness for configuration structures

I have a configuration file which gets read in, parsed and put into structures at the beginning of my programs run time.
The problem I am having is that I want these structures to be constant since the values in them should not change during the programs lifespan.
Currently I am doing the following:
config.h
#pragma warning(push)
#pragma warning(disable: 4510) /*-- we don't want a default constructor --*/
#pragma warning(disable: 4610) /*-- we don't want this to ever be user instantiated --*/
typedef struct SerialNode {
private:
void operator=(SerialNode&);
public:
const char* const port;
const char* const format;
} SerialNode;
#pragma warning(pop)
typedef std::map<const char*, const SerialNode*, MapStrComp> SerialMap;
SerialMap SerialConfig;
config.cpp
/*-- so we don't fall out of scope --*/
SerialNode* global_sn;
SerialNode local_sn = {port, format};
global_sn = new SerialNode(local_sn);
SerialConfig[key_store] = global_sn;
This works fine. However my problem is that now I am dealing with more complicated configuration data which requires me to pull a structure back out of the list, modify it and then put it back.
Obviously I can't modify it, so the solution would be something like:
SerialNode* global_sn;
SerialNode* old_sn = SerialConfig[key_store];
SerialNode local_sn = {port, format, old_sn->old_data, old_sn->more_old_data};
global_sn = new SerialNode(local_sn);
SerialConfig[key_store] = global_sn;
delete old_sn;
But this strikes me as bad programming practice. Is there is a better way to achieve what I'm going for which doesn't require such a hacked looking solution?
For reference, I'm using Visual Studio 2010
As always, the best thing you can do is not re-implement something that has already been written. There are a large number of libraries and frameworks that will help with serialization for c++:
Boost Serialization
Qt
Protocol Buffers
msgpack
Capn' Proto
Ideally the serialization framework you choose will exactly recreate the data graph that you are trying to store. Regardless of whether you have done any fixup, your goal will likely be to only provide const access to the global configuration data. Just make sure that mutators (including non const pointers) are not exposed via a header file.
The simple answer is what Thomas suggest, but correctly done (that is, not causing undefined behavior):
Create a mutable configuration object but pass it to the rest of the components by constant reference. When you create (and where you maintain) the real object you can change it, but the rest of the application won't be able to modify the config. A common pattern I have used in the past was:
class SomeObject {
Configuration const & config;
public:
SomeObject(Configuration const & config) : config(config) {}
void f() {
if (config.someParam()) { ...
// ...
void loadConfiguration(Config & config) { ... }
int main() {
Configuration config;
loadConfiguration(config); // config is a non-const &, can modify
SomeObject object(config); // object holds a const&, can only read
object.f();
// ...
This is not an answer to your question, just some observations to your code.
You don't need the typedef struct SerialNode { ... } SerialNode;, this is a c idiom. In c++, you just write struct SerialNode { ... }; and use SerialNode as a type name.
If you want to prevent a default constructor, make it private as you already do with the assignment operator
class SerialNode {
private:
SerialNode();
SerialNode &operator=(SerialNode&);
...
};
Don't use char* members, use std::string instead. C++ strings are much easier and safer to use than plain char pointers and the associated heap allocation.
Same goes for the map key; if you use std::string as a key, you don't need MapStrComp anymore, because std::string already provides an appropriate comparison.
Probably nicer is to wrap the whole thing in a singleton class:
class Config {
public:
static Config const& get() { return *config; }
static void load();
SerialNode const* operator[](const char*);
private:
static Config* config;
SerialMap map;
};
void Config::load() {
config = new Config();
// put things into it
}
Disclaimer: not tested, and haven't used C++ in a while, so there might be some syntax errors :)

Avoid creating multiple copies of code in memory

I'm new to developing on embedded systems and am not used to having very little program memory (16kB in this case) to play with. I would like to be able to create global variables, arrays, and functions that I can access from anywhere in the program while only existing in one place in memory. My current approach is to use static class members and methods that I can use by simply including the header file (e.g. #include "spi.h").
What is the best approach for what I'm trying to do?
Here is an example class. From what I understand, variables such as _callback and function definitions like call() in the .cpp will only appear in spi.o so they will appear only once in memory, but I may be mixed up.
spi.h:
#ifndef SPI_H_
#define SPI_H_
#include "msp430g2553.h"
class SPI {
public:
typedef void (*voidCallback)(void);
static voidCallback _callback;
static char largeArray[1000];
static __interrupt void USCIA0TX_ISR();
static void call();
static void configure();
static void transmitByte(unsigned char byte, voidCallback callback);
};
#endif /* SPI_H_ */
spi.cpp:
#include "spi.h"
SPI::voidCallback SPI::_callback = 0;
char SPI::largeArray[] = /* data */ ;
void SPI::configure() {
UCA0MCTL = 0;
UCA0CTL1 &= ~UCSWRST;
IE2 |= UCA0TXIE;
}
void SPI::transmitByte(unsigned char byte, voidCallback callback) {
_callback = callback;
UCA0TXBUF = byte;
}
void SPI::call() {
SPI::_callback();
}
#pragma vector=USCIAB0TX_VECTOR
__interrupt void SPI::USCIA0TX_ISR()
{
volatile unsigned int i;
while (UCA0STAT & UCBUSY);
SPI::call();
}
The data members and the member functions of the class you wrote will only be defined once in memory. And if they're not marked static, the member functions will still only be defined once in memory. Non-static data members will be created in memory once for each object that you create, so if you only create one SPI object you only get one copy of its non-static data members. Short version: you're solving a non-problem.
As per Pete, static won't affect code doubling up, only member vars. In your example, there is 0 difference between static non static memory usage except perhaps for the _callback var (which you call out as an error.) And that one variable would only double up if the class were created more than once.
If you want code to not exist in memory when not in use, look into overlays or some sort of dynamic linking process. DLL type code will probably be major overkill for 16K, but overlays with compressed code might help you out.
Also, beware of extra linked in code from libraries. Closely examine your .map files for code bloat from innocuous function calls. For instance, a single printf() call will link in all sorts of vargs stuff if it is the only thing using it. Same for software floating point (if you don't have a FP unit by default.)

Splitting a file and passing the data on to other classes

In my current project, I have a lot of binary files of different formats. Several of them act as simple archives, and therefore I am trying to come up with a good approach for passing extracted file data on to other classes.
Here's a simplified example of my current approach:
class Archive {
private:
std::istream &fs;
void Read();
public:
Archive(std::istream &fs); // Calls Read() automatically
~Archive();
const char* Get(int archiveIndex);
size_t GetSize(int archiveIndex);
};
class FileFormat {
private:
std::istream &fs;
void Read();
public:
FileFormat(std::istream &fs); // Calls Read() automatically
~FileFormat();
};
The Archive class basically parses the archive and reads the stored files into char pointers.
In order to load the first FileFormat file from an Archive, I would currently use the following code:
std::ifstream fs("somearchive.arc", std::ios::binary);
Archive arc(fs);
std::istringstream ss(std::string(arc.Get(0), arc.GetSize(0)), std::ios::binary);
FileFormat ff(ss);
(Note that some files in an archive could be additional archives but of a different format.)
When reading the binary data, I use a BinaryReader class with functions like these:
BinaryReader::BinaryReader(std::istream &fs) : fs(fs) {
}
char* BinaryReader::ReadBytes(unsigned int n) {
char* buffer = new char[n];
fs.read(buffer, n);
return buffer;
}
unsigned int BinaryReader::ReadUInt32() {
unsigned int buffer;
fs.read((char*)&buffer, sizeof(unsigned int));
return buffer;
}
I like the simplicity of this approach but I'm currently struggling with a lot of memory errors and SIGSEGVs and I'm afraid that it's because of this method. An example is when I create and read an archive repeatedly in a loop. It works for a large number of iterations, but after a while, it starts reading junk data instead.
My question to you is if this approach is feasible (in which case I ask what I am doing wrong), and if not, what better approaches are there?
The flaws of code in the OP are:
You are allocating heap memory and returning a pointer to it from one of your functions. This may lead to memory leaks. You have no problem with leaks (for now) but you must have such stuff in mind while designing your classes.
When dealing with Archive and FileFormat classes user always has to take into account the internal structure of your archive. Basically it compromises the idea of data incapsulation.
When user of your class framework creates an Archive object, he just gets a way to extract a pointer to some raw data. Then the user must pass this raw data to completely independent class. Also you will have more than one kind of FileFormat. Even without the need to watch for leaky heap allocations dealing with such system will be highly error-prone.
Lets try to apply some OOP principles to the task. Your Archive object is a container of Files of different format. So, an Archive's equivalent of Get() should generally return File objects, not a pointer to raw data:
//We gonna need a way to store file type in your archive index
enum TFileType { BYTE_FILE, UINT32_FILE, /*...*/ }
class BaseFile {
public:
virtual TFileType GetFileType() const = 0;
/* Your abstract interface here */
};
class ByteFile : public BaseFile {
public:
ByteFile(istream &fs);
virtual ~ByteFile();
virtual TFileType GetFileType() const
{ return BYTE_FILE; }
unsigned char GetByte(size_t index);
protected:
/* implementation of data storage and reading procedures */
};
class UInt32File : public BaseFile {
public:
UInt32File(istream &fs);
virtual ~UInt32File();
virtual TFileType GetFileType() const
{ return UINT32_FILE; }
uint32_t GetUInt32(size_t index);
protected:
/* implementation of data storage and reading procedures */
};
class Archive {
public:
Archive(const char* filename);
~Archive();
BaseFile* Get(int archiveIndex);
{ return (m_Files.at(archiveIndex)); }
/* ... */
protected:
vector<BaseFile*> m_Files;
}
Archive::Archive(const char* filename)
{
ifstream fs(filename);
//Here we need to:
//1. Read archive index
//2. For each file in index do something like:
switch(CurrentFileType) {
case BYTE_FILE:
m_Files.push_back(new ByteFile(fs));
break;
case UINT32_FILE:
m_Files.push_back(new UInt32File(fs));
break;
//.....
}
}
Archive::~Archive()
{
for(size_t i = 0; i < m_Files.size(); ++i)
delete m_Files[i];
}
int main(int argc, char** argv)
{
Archive arch("somearchive.arc");
BaseFile* pbf;
ByteFile* pByteFile;
pbf = arch.Get(0);
//Here we can use GetFileType() or typeid to make a proper cast
//An example of former:
switch ( pbf.GetFileType() ) {
case BYTE_FILE:
pByteFile = dynamic_cast<ByteFile*>(pbf);
ASSERT(pByteFile != 0 );
//Working with byte data
break;
/*...*/
}
//alternatively you may omit GetFileType() and rely solely on C++
//typeid-related stuff
}
Thats just a general idea of the classes that may simplify the usage of archives in your application.
Have in mind though that good class design may help you with memory leaks prevention, code clarification and such. But whatever classes you have you will still deal with binary data storage problems. For example, if your archive stores 64 bytes of byte data and 8 uint32's and you somehow read 65 bytes instead of 64, the reading of the following ints will give you junk. You may also encounter alignment and endianness problems (the latter is important if you applications are supposed to run on several platforms). Still, good class design may help you to produce a better code which addresses such problems.
It is asking for trouble to pass a pointer from your function and expect the user to know to delete it, unless the function name is such that it is obvious to do so, e.g. a function that begins with the word create.
So
Foo * createFoo();
is likely to be a function that creates an object that the user must delete.
A preferable solution would, for starters, be to return std::vector<char> or allow the user to pass std::vector<char> & to your function and you write the bytes into it, setting its size if necessary. (This is more efficient if doing multiple reads where you can reuse the same buffer).
You should also learn const-correctness.
As for your "after a while it fills with junk", where do you check for end of file?

C++ How to dynamically select a file handle according to the type of data that should be written?

I've got a class outputInterface; that should handle the output (to files) of some data. The data is contained in objects of some custom classes, say dataClassA and dataClassB, that all derive from a common base class dataClassBase.
Now I want the data to be written to different files according to its type. So data of type dataClassA should go to fileA, data of type dataClassB should go to fileB and so on. As this output happens very often I would like the file handles (fileA and fileB) to stay open, i.e. I don't want to open and close the files for the output of each piece of data. One outputInterface object can be expected to exist all the time.
So what I would like to achieve is something like this:
Dynamically associate data of type dataClassA with the file handle fileA etc.
When receiving data of type dataClassA check whether fileA is already connected to a file, if not, open the file.
How can I get this behavior (or least something similar / better)?
I've been thinking of making the file handles static members of dataClassA and dataClassB (or the base class dataClassBase?). But then, how do I take care of closing the files? I would have to somehow keep track of the data types that have actually been used (the files that have actually been opened).
Try something like this:
#ifndef OUTPUTINTERFACE?H
#define OUTPUTINTERFACE?H
#include <string>
#include <fstream>
#include <map>
class DataClass
{
public:
virtual bool WriteData(std::ofstream& FStream) = 0;
};
class DataClass1 :
public DataClass
{
virtual bool WriteData(std::ofstream& FStream)
{
FStream << "teletubbies";
}
};
class DataClass2 :
public DataClass
{
virtual bool WriteData(std::ofstream& FStream)
{
FStream << "garbage";
}
};
class OutputInterface
{
public:
OutputInterface()
{
}
~OutputInterface()
{
//Release stream pointers
}
template<typename T>
bool WriteData(T& Data)
{
std::string dClassUID = std::string(typeid(T).name);
tFStreamMap::iterator it this->streamMap.find(dClassUID);
std::ofstream* stream = NULL;
if(it != streamMap.end())
{
stream = it->second;
}
else
{
stream = new std::ofstream();
stream->open(dClassUID + ".txt");
streamMap.insert(std::make_pair(dClassUID, stream));
}
Data.WriteData(stream);
}
private:
typedef std::map<std::string, std::ofstream*> tFStreamMap;
tFStreamMap streamMap;
};
#endif
This is just a prove of concept and can be optimized in many ways.
I would rather stick with overloaded functions than with runtime type checks.
This is fairly easy to implement in C++11, using an
std::map<std::type_index, std::ostring*> outputMap. (In C++03, you'll have to
implement the equivalent of std::type_index yourself.) You get the
output stream using outputMap[typeid(*data)]. The only problem is
getting the streams into the map to begin with: you can do something
like:
std::ostream*& destPtr = outputMap[typeid(*data)];
if ( destPtr == NULL ) {
destPtr = new std::ofstream("...");
}
std::ostream& dest = *destPtr;
But from where do you get the filename?
There's also the question of when you close the streams: you can't
normally close an output stream in a destructor, since closing an output
stream is an operation which can fail, and you need to detect and react
to that failure. Probably with an exception, which is why you don't
want to do it in a destructor.
Since the "data" part comes from dataClassBase, you can make a virtual/pure-virtual function 'WriteData` in this class, and let derive class implement it.
The class outputInterface may take objects of type dataClassBase and would directly call WriteData. Other than WriteData you may also add other virtual functions in dataClassBase
You did not mention relationship between outputInterface and dataClassBase

serializing objects in C++ and storing as a blob type in mysql

I am using mysql/C++ connector to connect to a mysql database. I have some complex data structures so I need to serialize those and save in the database.
I tried something like the following.
vector<int> vectorTest(10,100);
istream *blob = NULL;
ostringstream os;
int size_data = sizeof(vector<int>);
blob = new istringstream((char*)&vectorTest, istringstream::in | istringstream::binary);
string qry = "INSERT INTO vector(id,object) VALUES (?,?)";
prep_stmt = con->prepareStatement(qry);
prep_stmt->setInt(1,1);
prep_stmt->setBlob(2,blob);
prep_stmt->execute();
I just tried a small example here. However the vector object is not getting saved.
Alternatively can I can use the following approach.
ostringstream os;
int size_data = sizeof(vector<int>);
os.write((char*)&vectorTest, size_data);
However I don't know how to redirect the outputstream to an inputstream, because the setBlob() method needs an istream as the input parameter.
Can I know how to get any of this examples working ? If my approach is incorrect can anyone provide a code example or improve the given code segment ? Your immediate response is greatly appreciated.
Thanks
You're going about this completely the wrong way. This isn't "serialization", in fact it's quite possibly the opposite of serialization -- it's just trying to write out a raw memory dump of a vector into the database. Imagine for a second that vector looked like something this:
struct vector_int {
unsigned int num_elements;
int* elements;
};
Where elements is a dynamically allocated array that holds the elements of the vector.
What you would end up writing out to your database is the value of num_elements and then the value of the pointer elements. The element data would not be written to the database, and if you were to load the pointer location back into a vector on a different run of your program, the location it points to would contain garbage. The same sort of thing will happen with std::vector since it contains dynamically allocated memory that will will be written out as pointer values in your case, and other internal state that may not be valid if reloaded.
The whole point of "serialization" is to avoid this. Serialization means turning a complex object like this into a sequence of bytes that contains all of the information necessary to reconstitute the original object. You need to iterate through the vector and write out each integer that's in it. And moreover, you need to devise a format where, when you read it back in, you can determine where one integer ends and the next begins.
For example, you might whitespace-delimit the ints, and write them out like this:
1413 1812 1 219 4884 -57 12
And then when you read this blob back in you would have to parse this string back into seven separate integers and insert them into a newly-created vector.
Example code to write out:
vector<int> vectorTest(10,100);
ostringstream os;
for (vector<int>::const_iterator i = vectorTest.begin(); i != vectorTest.end(); ++i)
{
os << *i << " ";
}
// Then insert os.str() into the DB as your blob
Example code to read in:
// Say you have a blob string called "blob"
vector<int> vectorTest;
istringstream is(blob);
int n;
while(is >> n) {
vectorTest.push_back(n);
}
Now, this isn't necessarily the most efficient approach, space-wise, since this turns your integers into strings before inserting them into the database, which will take much more space than if you had just inserted them as binary-coded integers. However, the code to write out and read in would be more complex in that case because you would have to concern yourself with how you pack the integers into a byte sequence and how you parse a byte sequence into a bunch of ints. The code above uses strings so that the standard library streams can make this part easy and give a more straightforward demonstration of what serialization entails.
My solution to writing to a MySQL database was to use the Visitor design pattern and an abstract base class. I did not use the BLOB data structure, instead used fields (columns):
struct Field
{
// Every field has a name.
virtual const std::string get_field_name(void) = 0;
// Every field value can be converted to a string (except Blobs)
virtual const std::string get_value_as_string(void) = 0;
// {Optional} Every field knows it's SQL type.
// This is used in creating the table.
virtual unsigned int get_sql_type(void) = 0;
// {Optional} Every field has a length
virtual size_t get_field_length(void) = 0;
};
I built a hierarchy including fields for numbers, bool, and strings. Given a Field pointer or reference, an SQL INSERT and SELECT statement can be generated.
A Record would be a container of fields. Just provide a for_each() method with a visitor:
struct Field_Functor
{
virtual void operator()(const Field& f) = 0;
};
struct Record
{
void for_each(Field_Functor& functor)
{
//...
functor(field_container[i]); // or something similar
}
};
By using a more true Visitor design pattern, the SQL specifics are moved into the visitor. The visitor knows the field attributes due to the method called. This reduces the Field structure to having only get_field_name and get_value_as_string methods.
struct Field_Integer;
struct Visitor_Base
{
virtual void process(const Field_Integer& fi) = 0;
virtual void process(const Field_String& fs) = 0;
virtual void process(const Field_Double& fd) = 0;
};
struct Field_With_Visitor
{
virtual void accept_visitor(Visitor_Base& vb) = 0;
};
struct Field_Integer
{
void accept_visitor(Visitor_Base& vb)
{
vb.process(*this);
}
};
The record using the `Visitor_Base`:
struct Record_Using_Visitor
{
void accept_visitor(Visitor_Base& vistor)
{
Field_Container::iterator iter;
for (iter = m_fields.begin();
iter != m_fields.end();
++iter)
{
(*iter)->accept_visitor(rv);
}
return;
}
};
My current hurdle is handling BLOB fields with MySQL C++ Connector and wxWidgets.
You may also want to add the tags: MySQL and database to your next questions.
boost has a serialization library (I have never used it tho)
or XML or JSON