1
\$\begingroup\$

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.

I left some casts like C-style casts because I see Alexandrescu is using the same and I am not alone :)

I also added a method for relative as(). It uses pointer and pointer arithmetic.

First here is some real usage:

/* BlobRef blob */

size_type const nodesCount = blob.size() / sizeof(Node);

const Node *nodes = blob.as<const Node>(0, nodesCount);

if (!nodes){
    log__("Problem, switch alternative");
    return false;
}

size_type pos = 0;

while(pos < nodesCount){
    const Node &node = nodes[pos];
    // ...
}

Here is the class:

#include <cstddef>  // size_t

#include <type_traits>  // is_pod

class BlobRef{
public:
    BlobRef() = default;

    BlobRef(const void *mem, size_t const size) noexcept :
                mem_( (const char *) mem ),
                size_(size){}

    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_;
    }

    void reset() noexcept{
        mem_ = nullptr;
        size_ = 0;
    }

public:
    const void *safeAccessMemory(size_t const pos, size_t const size) const noexcept{
        if (empty() || size == 0 || pos + size > size_)
            return nullptr;

        return & mem_[pos];
    }

    const void *safeAccessMemory(const void *ptr, size_t const size) const noexcept{
        const char *ptrc = (const char *) ptr;

        if (empty() || size == 0 || ptrc == nullptr || ptrc < mem_)
            return nullptr;

        /* long int */ auto const pos = ptrc - mem_;

        return safeAccessMemory((size_t) pos, size);
    }

public:
    template <class T>
    const T *as(size_t const pos, size_t const elements = 1) const noexcept{
        static_assert(std::is_pod<T>::value, "T must be POD type");

        return reinterpret_cast<const T *>( safeAccessMemory(pos, elements * sizeof(T)) );
    }

    template <class T>
    const T *as(const void *ptr, size_t const elements = 1) const noexcept{
        static_assert(std::is_pod<T>::value, "T must be POD type");

        return reinterpret_cast<const T *>( safeAccessMemory(ptr, elements * sizeof(T)) );
    }

    // ambiguous call guard fpr 0
    template <class T>
    const T *as(int const pos = 0, size_t const elements = 1) const noexcept{
        return as<T>( (size_t) pos, elements );
    }

private:
    const char  *mem_ = nullptr;
    size_t      size_ = 0;
};

Here are the tests:

#include "blobref.h"

#include <cstdint>
#include <cstring>

#include <assert.h>
#include <endian.h>

#include <iostream>
#include <iomanip>

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', 5);
        assert(strncmp(s, "abcde", 5) == 0);

        // relative
        assert(*br.as<char>(s + 5) == 'f');
    }

    {
        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         );
        assert(st->s[0] == 0x53         );
        assert(st->s[1] == 0x54         );
        assert(st->s[2] == 0x55         );
        assert(st->s[3] == 0x56         );

        // relative
        const char *stc = (const char *) st;
        assert(*br.as<char>(stc + sizeof(TestStruct)) == 0x57);
    }

    {
        size_t const max = SIZE / sizeof(uint64_t);

        const uint64_t *u64 = br.as<uint64_t>(0, max);
        assert(u64 != nullptr   );
        assert(u64[      0] == htobe64(0x0001020304050607)  );
        assert(u64[max - 1] == htobe64(0xf8f9fafbFCFDFEFF)  );

        // relative
        const uint64_t *p = & u64[max - 2];
        assert(*br.as<uint64_t>(p + 1) == u64[max - 1]  );
    }

    {
        size_t const max = SIZE / sizeof(uint64_t) ;

        // after last
        assert(br.as<uint64_t>(0, max + 1 ) == nullptr      );
    }

    {
        size_t const max = SIZE / sizeof(uint64_t) ;

        const uint64_t *u64 = br.as<uint64_t>(0, max);

        // relative
        // after last
        const uint64_t *p = & u64[max - 1];
        assert(br.as<uint64_t>(p + 1) == nullptr    );
    }

    // zero size
    assert(br.as<char>(0x00, 0) == nullptr  );

    // nullptr
    assert(br.as<char>(nullptr) == nullptr  );

    return 0;
}

int main(){
    test_blobref();
}
\$\endgroup\$
  • \$\begingroup\$ "I left some casts like C-style casts because I see Alexandrescu is using the same and I am not alone :)" that's a pretty awful reason to use something that's objectively worse. \$\endgroup\$ – cloakedlearning Sep 12 '16 at 22:05
  • \$\begingroup\$ on simple types, I do not see a reason to use C++ type casts. why I should use reinterpred cast on int to unsigned, since I can type less and have same level of readability and same generated code at the end? This does not apply for classes and const cast. But you are probably right to say this :) \$\endgroup\$ – Nick Sep 13 '16 at 5:59
  • 1
    \$\begingroup\$ Typing trivially less code is also a poor reason in my estimation. C++ casts provide better safety, signal intent better, catch bugs at compile time and are easier to search for. Also for casting an int to unsigned you would use a static_cast so I think it's likely you do not understand these casts properly. \$\endgroup\$ – cloakedlearning Sep 13 '16 at 8:35
  • \$\begingroup\$ Apologies, in the above comment I made a mistake. Converting int to unsigned is well defined and doesn't need a cast. The general points stands though. \$\endgroup\$ – cloakedlearning Sep 13 '16 at 8:50
  • \$\begingroup\$ Converting int to unsigned give warning. I do not like them. But your point is well taken. \$\endgroup\$ – Nick Sep 13 '16 at 8:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.