Нет простого решения этой проблемы. Обычно вы можете создавать отдельные структуры и указывать компилятору плотно их упаковать, например:
/* GNU has attributes */
struct PackedData {
char cmember;
int imember;
} __attribute__((packed));
или
/* MSVC has headers and #pragmas */
#include <pshpack1.h>
struct PackedData {
char cmember;
int imember;
};
#include <poppack.h>
Затем вам нужно написать код, который преобразует ваши неупакованные структуры в упакованные структуры и наоборот. Если вы используете C ++, вы можете создавать вспомогательные функции шаблона, которые основаны на типе структуры, а затем специализировать их:
template <typename T>
std::ostream& encode_to_stream(std::ostream& os, T const& object) {
return os.write((char const*)&object, sizeof(object));
}
template <typename T>
std::istream& decode_from_stream(std::istream& is, T& object) {
return is.read((char*)&object, sizeof(object));
}
template<>
std::ostream& encode_to_stream<Data>(std::ostream& os, Data const& object) {
encode_to_stream<char>(os, object.cmember);
encode_to_stream<int>(os, object.imember);
return os;
}
template <>
std::istream& decode_from_stream<Data>(std::istream& is, Data& object) {
decode_from_stream<char>(is, object.cmember);
decode_from_stream<int>(is, object.imember);
return is;
}
Бонус заключается в том, что по умолчанию будут считываться и записываться объекты POD, включая отступы. Вы можете специализироваться по мере необходимости, чтобы оптимизировать хранение. Тем не менее, вы, вероятно, захотите рассмотреть также вопросы порядка байтов, управления версиями и других бинарных хранилищ. Было бы разумно просто написать архивный класс, который обернет ваше хранилище и предоставит методы для сериализации и десериализации примитивов, а затем метод с открытым концом, который вы можете специализировать по мере необходимости:
class Archive {
protected:
typedef unsigned char byte;
void writeBytes(byte const* byte_ptr, std::size_t byte_size) {
m_fstream.write((char const*)byte_ptr, byte_size);
}
public:
template <typename T>
void writePOD(T const& pod) {
writeBytes((byte const*)&pod, sizeof(pod));
}
// Users are required to specialize this to use it. If it is used
// for a type that it is not specialized for, a link error will occur.
template <typename T> void serializeObject(T const& obj);
};
template<>
void Archive::serializeObject<Data>(Data const& obj) {
writePOD(cmember);
writePOD(imember);
}
Это подход, к которому я всегда прибегал после множества пертурбаций между ними. Он легко расширяется, не требуя наследования, и дает вам гибкость, позволяющую при необходимости изменять базовый формат хранения данных. Вы даже можете специализировать writePOD
, чтобы делать разные вещи для разных базовых типов данных, например, гарантировать, что многобайтовые целые числа записываются в сетевом порядке или еще много чего.