Problem
This class was inspired from Microsoft’s array_view
.
It intended to use with mmap
-ed memory for easily check bounds and so on.
I probably will need to throw exceptions, but this is not my usual way of working.
Any comments are welcome.
#include <cstring>
class BlobRef{
public:
BlobRef(const void *mem, size_t const size) noexcept :
mem_( (const char *) mem),
size_(size){}
// for test purposes
BlobRef(const char *s) noexcept :
BlobRef(s, strlen(s) + 1){}
template<size_t N>
BlobRef(const char(&mem)[N]) noexcept:
BlobRef(mem, N){}
public:
bool empty() const noexcept{
return mem_ == nullptr || size_ == 0;
}
size_t size() const noexcept{
return size_;
}
const void *data_() const noexcept{
return mem_;
}
public:
const void *safeAccessMemory(size_t const pos, size_t const size) const noexcept{
if (pos + size <= size_)
return & mem_[pos];
else
return nullptr;
}
template <class T>
const T *as(size_t const pos = 0, size_t const elements = 1) const noexcept{
return (const T *) safeAccessMemory(pos, elements * sizeof(T));
}
private:
const char *mem_ = nullptr;
size_t size_ = 0;
};
#include <cstdint>
#include <assert.h>
#include <endian.h>
int test_blobref(){
constexpr size_t SIZE = 256;
char mem[SIZE];
for(int i = 0; i < SIZE; ++i)
mem[i] = (char) i;
BlobRef br{ mem };
assert(*br.as<uint16_t>(0x00) == htobe16(0x0001) );
assert(*br.as<uint16_t>(0x0E) == htobe16(0x0E0F) );
assert(*br.as<uint32_t>(0x10) == htobe32(0x10111213) );
{
const char *s = br.as<char>('a');
assert(strncmp(s, "abcde", 5) == 0);
}
{
struct TestStruct{
uint16_t i;
char c;
char s[4];
}__attribute__((__packed__));
const TestStruct *st = br.as<TestStruct>(0x50);
assert(st->i == htobe16(0x5051) );
assert(st->c == 0x52 );
const char *s1 = st->s;
const char s2[] = { 0x53, 0x54, 0x55, 0x56, 0x57 };
assert(strncmp(s1, s2, sizeof s2) == 0);
}
{
size_t const pos = SIZE / sizeof(uint64_t);
const uint64_t *u64 = br.as<uint64_t>(0, pos);
assert(u64 != nullptr );
assert(u64[ 0] == htobe64(0x0001020304050607) );
assert(u64[pos - 1] == htobe64(0xf8f9fafbFCFDFEFF) );
}
{
size_t const pos = SIZE / sizeof(uint64_t) ;
const uint64_t *u64 = br.as<uint64_t>(0, pos + 1 );
assert(u64 == nullptr );
}
return 0;
}
int main(){
test_blobref();
}
Solution
BlobRef details
- You should provide a default constructor.
safeAccessMemory
contains a bug. If you have an emptyBlobRef
and you callsafeAccessMemory(0, 0)
you will deference a nullptr (the guard fails).- Don’t use c-style casts. In your cases you should use
reinterpret_cast
. This helps readability, search-ability and signals clear intent. as
can invoke undefined behaviour if called on certain types. The gritty details are in 3.10.10 of the C++ spec.
If a program attempts to access the stored value of an object through
a glvalue of other than one of the following types the behavior is
undefined:the dynamic type of the object.
a cv-qualified version of the dynamic type of the object.
a type similar (as defined in 4.4) to the dynamic type of the object.
a type that is the signed or unsigned type corresponding to the dynamic type of the object.
a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object.
an aggregate or union type that includes one of the aforementioned types among its elements or nonstatic data members (including, recursively, an element or non-static data member of a subaggregate or contained union), a type that is a (possibly cv-qualified) base class type of the dynamic type of the object.
a char or unsigned char type.
My understand is that there’s no easy way to make this work (the gsl folks removed theirs)
Unfortunately this would remove the entire essence of this class. One approach you could take to remedy this would be to use type-traits to restrict the function to types for which it will be well defined.
template <class T, typename = std::enable_if_t<std::is_integral<T>::value>>
const T *as(size_t const pos = 0, size_t const elements = 1) const noexcept{
return (const T *) safeAccessMemory(pos, elements * sizeof(T));
}