Có một mẫu chung mà bạn có thể sử dụng để tuần tự hóa các đối tượng. Nguyên thủy Fundemental là hai hàm này bạn có thể đọc và ghi từ các trình vòng lặp:
template <class OutputCharIterator>
void putByte(char byte, OutputCharIterator &&it)
{
*it = byte;
++it;
}
template <class InputCharIterator>
char getByte(InputCharIterator &&it, InputCharIterator &&end)
{
if (it == end)
{
throw std::runtime_error{"Unexpected end of stream."};
}
char byte = *it;
++it;
return byte;
}
Sau đó, các chức năng tuần tự hóa và giải mã hóa tuân theo mẫu:
template <class OutputCharIterator>
void serialize(const YourType &obj, OutputCharIterator &&it)
{
}
template <class InputCharIterator>
void deserialize(YourType &obj, InputCharIterator &&it, InputCharIterator &&end)
{
}
Đối với các lớp, bạn có thể sử dụng mẫu hàm bạn bè để cho phép tìm thấy quá tải bằng cách sử dụng ADL:
class Foo
{
int internal1, internal2;
template <class OutputCharIterator>
friend void serialize(const Foo &obj, OutputCharIterator &&it)
{
}
};
Trong chương trình của bạn, bạn có thể tuần tự hóa và đối tượng thành một tệp như sau:
std::ofstream file("savestate.bin");
serialize(yourObject, std::ostreambuf_iterator<char>(file));
Sau đó đọc:
std::ifstream file("savestate.bin");
deserialize(yourObject, std::istreamBuf_iterator<char>(file), std::istreamBuf_iterator<char>());
Câu trả lời cũ của tôi ở đây:
Serialization có nghĩa là biến đối tượng của bạn thành dữ liệu nhị phân. Trong khi deserialization có nghĩa là tạo lại một đối tượng từ dữ liệu.
Khi tuần tự hóa, bạn đang đẩy các byte vào một uint8_t
vector. Khi hủy số hóa, bạn đang đọc các byte từ mộtuint8_t
vectơ.
Chắc chắn có những mẫu bạn có thể sử dụng khi sắp xếp thứ tự.
Mỗi lớp có thể nối tiếp hóa phải có serialize(std::vector<uint8_t> &binaryData)
hoặc hàm có ký tự tương tự sẽ ghi biểu diễn nhị phân của nó vào vectơ được cung cấp. Sau đó, hàm này có thể chuyển vectơ này xuống các hàm tuần tự hóa của thành viên để họ cũng có thể viết nội dung của mình vào đó.
Vì cách biểu diễn dữ liệu có thể khác nhau trên các kiến trúc khác nhau. Bạn cần tìm ra một lược đồ cách biểu diễn dữ liệu.
Hãy bắt đầu từ những điều cơ bản:
Sắp xếp thứ tự dữ liệu số nguyên
Chỉ cần viết các byte theo thứ tự endian nhỏ. Hoặc sử dụng biểu diễn biến thể nếu kích thước quan trọng.
Serialization theo thứ tự endian nhỏ:
data.push_back(integer32 & 0xFF);
data.push_back((integer32 >> 8) & 0xFF);
data.push_back((integer32 >> 16) & 0xFF);
data.push_back((integer32 >> 24) & 0xFF);
Hủy đăng ký từ trật tự endian nhỏ:
integer32 = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
Tuần tự hóa dữ liệu dấu phẩy động
Theo như tôi biết IEEE 754 có độc quyền ở đây. Tôi không biết bất kỳ kiến trúc chính thống nào sẽ sử dụng thứ gì khác cho phao nổi. Điều duy nhất có thể khác là thứ tự byte. Một số kiến trúc sử dụng ít endian, những kiến trúc khác sử dụng thứ tự byte endian lớn. Điều này có nghĩa là bạn cần phải cẩn thận thứ tự nào để bạn tăng byte ở đầu nhận. Một sự khác biệt khác có thể là xử lý các giá trị không chuẩn và vô cực và NAN. Nhưng miễn là bạn tránh những giá trị này, bạn sẽ ổn.
Serialization:
uint8_t mem[8];
memcpy(mem, doubleValue, 8);
data.push_back(mem[0]);
data.push_back(mem[1]);
...
Deserialization đang làm nó lạc hậu. Lưu ý thứ tự byte của kiến trúc của bạn!
Serializing chuỗi
Trước tiên, bạn cần đồng ý về một bảng mã. UTF-8 là phổ biến. Sau đó, lưu trữ nó dưới dạng tiền tố độ dài: đầu tiên bạn lưu trữ độ dài của chuỗi bằng phương pháp tôi đã đề cập ở trên, sau đó viết chuỗi theo từng byte.
Nối tiếp các mảng.
Chúng giống như một chuỗi. Đầu tiên bạn tuần tự hóa một số nguyên đại diện cho kích thước của mảng sau đó tuần tự hóa từng đối tượng trong đó.
Nối tiếp toàn bộ các đối tượng
Như tôi đã nói trước đây, họ nên có một serialize
phương thức thêm nội dung vào một vector. Để giải mã một đối tượng, nó phải có một hàm tạo nhận luồng byte. Nó có thể là một istream
nhưng trong trường hợp đơn giản nhất, nó có thể chỉ là một tham chiếuuint8_t
con trỏ . Hàm tạo đọc các byte mà nó muốn từ luồng và thiết lập các trường trong đối tượng. Nếu hệ thống được thiết kế tốt và tuần tự hóa các trường theo thứ tự trường đối tượng, bạn chỉ có thể chuyển luồng tới các hàm tạo của trường trong danh sách trình khởi tạo và sắp xếp chúng được giải mã theo đúng thứ tự.
Sắp xếp thứ tự các đồ thị đối tượng
Trước tiên, bạn cần chắc chắn rằng những đối tượng này có thực sự là thứ mà bạn muốn tuần tự hóa hay không. Bạn không cần phải tuần tự hóa chúng nếu các trường hợp của các đối tượng này xuất hiện trên đích.
Bây giờ bạn phát hiện ra rằng bạn cần phải tuần tự hóa đối tượng được trỏ bởi một con trỏ. Vấn đề của con trỏ rằng chúng chỉ hợp lệ trong chương trình sử dụng chúng. Bạn không thể tuần tự hóa con trỏ, bạn nên ngừng sử dụng chúng trong các đối tượng. Thay vào đó, hãy tạo các nhóm đối tượng. Nhóm đối tượng này về cơ bản là một mảng động chứa các "hộp". Các hộp này có số lượng tham chiếu. Số tham chiếu khác 0 cho biết một đối tượng đang hoạt động, 0 cho biết một vị trí trống. Sau đó, bạn tạo con trỏ thông minh tương tự như shared_ptr không lưu trữ con trỏ đến đối tượng, mà là chỉ mục trong mảng. Bạn cũng cần đồng ý về một chỉ mục biểu thị con trỏ null, ví dụ. -1.
Về cơ bản những gì chúng tôi đã làm ở đây là thay thế các con trỏ bằng các chỉ mục mảng. Bây giờ khi tuần tự hóa, bạn có thể tuần tự hóa chỉ mục mảng này như bình thường. Bạn không cần phải lo lắng về vị trí đối tượng sẽ ở trong bộ nhớ trên hệ thống đích. Chỉ cần đảm bảo rằng chúng cũng có cùng một nhóm đối tượng.
Vì vậy, chúng ta cần tuần tự hóa các nhóm đối tượng. Nhưng những cái nào? Khi bạn tuần tự hóa một đồ thị đối tượng, bạn không chỉ tuần tự hóa một đối tượng, bạn đang tuần tự hóa toàn bộ hệ thống. Điều này có nghĩa là việc tuần tự hóa hệ thống không nên bắt đầu từ các phần của hệ thống. Những đối tượng đó không nên lo lắng về phần còn lại của hệ thống, chúng chỉ cần tuần tự hóa các chỉ mục mảng và thế là xong. Bạn nên có một quy trình tuần tự hóa hệ thống sắp xếp việc tuần tự hóa hệ thống và đi qua các nhóm đối tượng liên quan và tuần tự hóa tất cả chúng.
Ở đầu nhận, tất cả các mảng chứa các đối tượng bên trong được giải hóa, tạo lại đồ thị đối tượng mong muốn.
Tuần tự hóa các con trỏ hàm
Không lưu trữ con trỏ trong đối tượng. Có một mảng tĩnh chứa các con trỏ đến các hàm này và lưu trữ chỉ mục trong đối tượng.
Vì cả hai chương trình đều có bảng này được biên dịch thành giá đỡ, nên chỉ sử dụng chỉ mục sẽ hoạt động.
Nối tiếp các kiểu đa hình
Vì tôi đã nói rằng bạn nên tránh các con trỏ trong các loại có thể tuần tự hóa và thay vào đó bạn nên sử dụng chỉ mục mảng, tính đa hình không thể hoạt động, vì nó yêu cầu con trỏ.
Bạn cần giải quyết vấn đề này với các thẻ loại và liên kết.
Phiên bản
Trên tất cả những điều trên. Bạn có thể muốn các phiên bản khác nhau của phần mềm tương thích với nhau.
Trong trường hợp này, mỗi đối tượng nên viết một số phiên bản vào đầu tuần tự của chúng để chỉ ra phiên bản.
Khi tải lên đối tượng ở phía bên kia, các đối tượng mới hơn có thể có thể xử lý các biểu diễn cũ hơn nhưng các đối tượng cũ hơn không thể xử lý mới hơn, vì vậy chúng nên đưa ra một ngoại lệ về điều này.
Mỗi khi có gì đó thay đổi, bạn nên tăng số phiên bản.
Vì vậy, để kết thúc điều này, tuần tự hóa có thể phức tạp. Nhưng may mắn thay, bạn không cần phải tuần tự hóa mọi thứ trong chương trình của mình, hầu hết chỉ các thông báo giao thức được tuần tự hóa, thường là các cấu trúc cũ đơn giản. Vì vậy, bạn không cần các thủ thuật phức tạp mà tôi đã đề cập ở trên quá thường xuyên.