标题不言自明:我想将任意数量的可复制的非数组“填充物”序列化到缓冲区中(出于学术原因)。
基本思想是重新解释要序列化为unsigned char* 的地址,然后使用std::copy到该缓冲区,并执行相反的操作来加载。
下面是一个最小的非工作示例。
#include <iostream>
struct A
{
int i, j;
};
int main()
{
int i = -1;
A a {23, 42};
unsigned char* buffer = new unsigned char[20];
const unsigned char* to_serialize = reinterpret_cast<const unsigned char*>(&i);
std::copy(to_serialize, to_serialize + sizeof(int), buffer);
int offset = sizeof(int);
to_serialize = reinterpret_cast<const unsigned char*>(&a);
std::copy(to_serialize, to_serialize + sizeof(A), buffer + offset);
unsigned char serialized_i[sizeof(int)];
std::copy(buffer, buffer + sizeof(int), serialized_i);
int ii (*reinterpret_cast<int*>(serialized_i));
std::cout << ii << std::endl; //outputs -1 -> ok
unsigned char serialized_a[sizeof(A)];
std::copy(buffer + offset, buffer + offset + sizeof(A), serialized_a);
A aa(*reinterpret_cast<A*>(serialized_a));
std::cout << aa.i << " " << aa.j << std::endl; //suspect there is a bug
}
如果我只写一个A,它就能正常工作;如果我写任意数量的int,它也能正常工作;但是一旦我把两者混合起来(如上所述),它就失败了。
在上面的例子中,它“似乎”工作,但在完整的代码(如下)中,它没有工作。
文件字节缓冲区. h
#ifndef BYTEBUFFER_H
#define BYTEBUFFER_H
#include <vector>
#include <stdexcept>
#include <iostream>
#include <memory>
template<class T>
concept TriviallyCopyable = ! std::is_array_v<T> && std::is_trivially_copyable_v<T>;
using byte = unsigned char;
class ByteBuffer
{
byte * _buffer;
std::vector<size_t> _offsets;
size_t _size;
size_t _capacity;
public:
ByteBuffer(size_t capacity = 100);
//Rule of 5 with copy and swap idiom
~ByteBuffer();
ByteBuffer(const ByteBuffer& buffer);
ByteBuffer& operator=(const ByteBuffer& buffer);
ByteBuffer(ByteBuffer&& buffer) noexcept;
ByteBuffer& operator=(ByteBuffer&& buffer) noexcept;
void swap(ByteBuffer& buffer) noexcept;
//basic accessors
const byte* buffer() const;
const std::vector<size_t>& offsets() const;
size_t size() const; //size in bytes
size_t capacity() const; //capacity in bytes
bool reserve(size_t max_size);
void clear();
//storage
template<TriviallyCopyable T>
size_t store(const T& t);
template<TriviallyCopyable T>
T load(size_t offset) const;
//I need to write overloads for arrays
void dump(std::ostream& out, bool clear = true);
};
void swap(ByteBuffer& buffer1, ByteBuffer& buffer2) noexcept;
///////////////////////////////////////////////////////// IMPLEMENTATIONS
template<TriviallyCopyable T>
size_t ByteBuffer::store(const T& t)
{
if(_buffer == nullptr)
throw std::runtime_error("Storage buffer is nullptr");
if(_offsets.back() + sizeof(T) > _capacity)
reserve(2 * _capacity);
size_t offset = _offsets.back() + sizeof(T); //where we need to store
const byte* serialized = reinterpret_cast<const byte*>(std::addressof(t)); //better than &t
std::copy(serialized, serialized + sizeof(T), _buffer + offset);
_offsets.push_back(offset);
_size += offset;
return offset;
}
template<TriviallyCopyable T>
T ByteBuffer::load(size_t offset) const
{
if(_buffer == nullptr)
throw std::runtime_error("Storage buffer is nullptr");
byte serialized[sizeof(T)];
std::copy(_buffer + offset, _buffer + offset + sizeof(T), serialized);
return T(*reinterpret_cast<T*>(serialized)); //force copy
}
#endif
文件字节缓冲区. cpp
#include "bytebuffer.h"
#include <algorithm>
ByteBuffer::ByteBuffer(size_t capacity) : _buffer(capacity > 0 ? new byte[capacity] : nullptr), _offsets({0}), _size(0), _capacity(capacity)
{//remark: there is always at least '0' in the offset list, because when the buffer is empty, the first free spot has offset 0
if(capacity > 0)
std::fill(_buffer, _buffer + capacity, 0);
}
const unsigned char* ByteBuffer::buffer() const //alias not working here, since it's out of scope
{
return _buffer;
}
const std::vector<size_t>& ByteBuffer::offsets() const
{
return _offsets;
}
size_t ByteBuffer::size() const
{
return _size;
}
size_t ByteBuffer::capacity() const
{
return _capacity;
}
bool ByteBuffer::reserve(size_t max_size)
{
if(max_size <= _capacity)
return false;
try
{
byte* newbuffer = new byte[max_size]; //might throw
std::copy(_buffer, _buffer + _size, newbuffer);
_capacity = max_size;
delete[] _buffer;
_buffer = newbuffer;
return true;
}
catch(...)
{
return false;
}
}
void ByteBuffer::clear()
{
std::fill(_buffer, _buffer + _capacity, 0);
_offsets.clear();
_offsets.push_back(0);
_size = 0;
}
void ByteBuffer::dump(std::ostream& out, bool clear)
{
std::for_each(_buffer, _buffer + _size, [&out](byte c){ out << c; });
if(clear)
this->clear();
}
std::ostream& operator<<(std::ostream& out, ByteBuffer& buffer)
{
buffer.dump(out, false);
return out;
}
/////////////////////////// RULE OF 5
void ByteBuffer::swap(ByteBuffer& other) noexcept
{
std::swap(_buffer, other._buffer);
std::swap(_offsets, other._offsets);
std::swap(_size, other._size);
std::swap(_capacity, other._capacity);
}
void swap(ByteBuffer& buffer1, ByteBuffer& buffer2) noexcept
{
buffer1.swap(buffer2);
}
ByteBuffer::~ByteBuffer()
{
if(_buffer)
{
delete[] _buffer;
_buffer = nullptr;
}
}
ByteBuffer::ByteBuffer(const ByteBuffer& other) : _buffer(new byte[other._capacity]), _offsets(other._offsets), _size(other._size), _capacity(other._capacity)
{
std::copy(other._buffer, other._buffer + _capacity, _buffer);
}
ByteBuffer& ByteBuffer::operator=(const ByteBuffer& other)
{
ByteBuffer tmp(other);
swap(tmp);
return *this;
}
ByteBuffer::ByteBuffer(ByteBuffer&& other) noexcept : ByteBuffer()
{
swap(other);
}
ByteBuffer& ByteBuffer::operator=(ByteBuffer&& other) noexcept
{
swap(other);
if(other._buffer != nullptr)
{
delete[] other._buffer;
other._buffer = nullptr;
}
return *this;
}
文件main.cpp
#include <iostream>
#include "bytebuffer.h"
#include <type_traits>
#include <iomanip>
using namespace std;
struct A
{
int i, j;
};
int main()
{
ByteBuffer buffer;
size_t offset1 = buffer.store(2);
size_t offset2 = buffer.store(3.5);
A a {23, 42};
cout << "sizeof A : " << sizeof(a) << endl;
cout << "A has " << a.i << " " << a.j << endl;
cout << boolalpha << is_trivially_copyable<A>::value << endl << endl;
size_t offset3 = buffer.store(a);
size_t offset4 = buffer.store('d');
//size_t offset5 = buffer.store("Hello"); //doesn't work with arrays, I need a specific template overload
//size_t offset6 = buffer.store(std::string("Hello")); //doesn't compile, and that's ok
//no template argument deduction possible down here, that's normal, and ok
cout << buffer.load<int>(offset1) << endl; //2
cout << buffer.load<double>(offset2) << endl; //3.5
A loaded = buffer.load<A>(offset3); //runs, but wrong atrtibutes
cout << loaded.i << " " << loaded.j << endl; //error: wrong value
cout << buffer.load<char>(offset4) << endl; //d
//cout << buffer.load<const char[6]>(offset5) << endl; //doesn't work with arrays, I need a specific template overload
//cout << buffer.load<std::string>(offset6) << endl; //doesn't compile, and that's ok
}
1条答案
按热度按时间camsedfj1#
所以,第一个MWE工作得很好,就像评论中说的那样。而且,为了完整修复,错误是我如何在
_offset
属性中存储偏移量。我将
store
成员函数重写如下: