c++ 将任意可复制的非数组“填充”序列化到缓冲区(reinterpret_cast)出错

dwbf0jvd  于 2023-02-01  发布在  其他
关注(0)|答案(1)|浏览(102)

标题不言自明:我想将任意数量的可复制的非数组“填充物”序列化到缓冲区中(出于学术原因)。
基本思想是重新解释要序列化为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
}
camsedfj

camsedfj1#

所以,第一个MWE工作得很好,就像评论中说的那样。而且,为了完整修复,错误是我如何在_offset属性中存储偏移量。
我将store成员函数重写如下:

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(); //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 + sizeof(T));
    _size += sizeof(T);
    return offset;
}

相关问题