この答えは2つのことを前提としています。
- バイナリブロブをしっかりと(穴なしで)パックする必要があります。
- データメンバーに整列されていない方法でアクセスすることは望ましくありません(これは、コンパイラーがデフォルトで必要とする方法で整列されているデータメンバーにアクセスする場合に比べて低速です)。
この場合、大きな「blob」をバイト指向のストリームとして扱う設計を検討する必要があります。このストリームでは、自然に配置されたオブジェクトにデータを入力するタグ/値のペアをマーシャル/デマーシャルします。
このスキームを使用すると、両方の長所を活用できます。密集したblobを取得しますが、blobからオブジェクトを抽出すると、自然に配置されるため、オブジェクトメンバーへのアクセスが高速になります。また、移植性があり1、コンパイラ拡張機能に依存しません。欠点は、ブロブに入れることができるすべてのタイプに対して記述する必要のある定型コードです。
初歩的な例:
#include <cassert>
#include <iomanip>
#include <iostream>
#include <stdint.h>
#include <vector>
enum BlobKey
{
kBlobKey_Widget,
kBlobKey_Gadget
};
class Blob
{
public:
Blob() : cursor_(0) {}
// Extract a value from the blob. The key associated with this value should
// already have been extracted.
template <typename T>
Blob& operator>>(T& value)
{
assert(cursor_ < bytes_.size());
char* dest = reinterpret_cast<char*>(&value);
for (size_t i=0; i<sizeof(T); ++i)
dest[i] = bytes_[cursor_++];
return *this;
}
// Insert a value into the blob
template <typename T>
Blob& operator<<(const T& value)
{
const char* src = reinterpret_cast<const char*>(&value);
for (size_t i=0; i<sizeof(T); ++i)
bytes_.push_back(src[i]);
return *this;
}
// Overloads of << and >> for std::string might be useful
bool atEnd() const {return cursor_ >= bytes_.size();}
void rewind() {cursor_ = 0;}
void clear() {bytes_.clear(); rewind();}
void print() const
{
using namespace std;
for (size_t i=0; i<bytes_.size(); ++i)
cout << setfill('0') << setw(2) << hex << int(bytes_[i]) << " ";
std::cout << "\n" << dec << bytes_.size() << " bytes\n";
}
private:
std::vector<uint8_t> bytes_;
size_t cursor_;
};
class Widget
{
public:
explicit Widget(int a=0, short b=0, char c=0) : a_(a), b_(b), c_(c) {}
void print() const
{
std::cout << "Widget: a_=" << a_ << " b=" << b_
<< " c_=" << c_ << "\n";
}
private:
int a_;
short b_;
long c_;
friend Blob& operator>>(Blob& blob, Widget& widget)
{
// Demarshall members from blob
blob >> widget.a_;
blob >> widget.b_;
blob >> widget.c_;
return blob;
};
friend Blob& operator<<(Blob& blob, Widget& widget)
{
// Marshall members to blob
blob << kBlobKey_Widget;
blob << widget.a_;
blob << widget.b_;
blob << widget.c_;
return blob;
};
};
class Gadget
{
public:
explicit Gadget(long a=0, char b=0, short c=0) : a_(a), b_(b), c_(c) {}
void print() const
{
std::cout << "Gadget: a_=" << a_ << " b=" << b_
<< " c_=" << c_ << "\n";
}
private:
long a_;
int b_;
short c_;
friend Blob& operator>>(Blob& blob, Gadget& gadget)
{
// Demarshall members from blob
blob >> gadget.a_;
blob >> gadget.b_;
blob >> gadget.c_;
return blob;
};
friend Blob& operator<<(Blob& blob, Gadget& gadget)
{
// Marshall members to blob
blob << kBlobKey_Gadget;
blob << gadget.a_;
blob << gadget.b_;
blob << gadget.c_;
return blob;
};
};
int main()
{
Widget w1(1,2,3), w2(4,5,6);
Gadget g1(7,8,9), g2(10,11,12);
// Fill blob with widgets and gadgets
Blob blob;
blob << w1 << g1 << w2 << g2;
blob.print();
// Retrieve widgets and gadgets from blob
BlobKey key;
while (!blob.atEnd())
{
blob >> key;
switch (key)
{
case kBlobKey_Widget:
{
Widget w;
blob >> w;
w.print();
}
break;
case kBlobKey_Gadget:
{
Gadget g;
blob >> g;
g.print();
}
break;
default:
std::cout << "Unknown object type in blob\n";
assert(false);
}
}
}
Boostを使用できる場合は、この回答のように、バイナリメモリストリームでBoost.Serializationを使用することをお勧めします。
(1)ポータブルとは、ソースコードをどこでもコンパイルできることを意味します。結果のバイナリブロブは、エンディアンと整数サイズが異なる他のマシンに転送された場合、移植できなくなります。