0
\$\begingroup\$

For my game engine I needed a simple templated array class that can be safely passed across module boundaries. It's not supposed to be a replacement for std::vector and thus doesn't need all of its features. Instead it is intended to be used in the interface where binary compatibility is important. For malloc and free I use wrapper functions that are exported from my main core library but in order to have a working example I replaced them with the standard functions here.

#pragma once

#include <cstdint>
#include <cassert>
#include <type_traits>
//#include "memory.h"

// memory.h
//==========================================
namespace lel::core{
    inline void* checked_malloc(std::size_t size){
        auto mem = malloc(size);

        if(!mem)
            throw std::bad_alloc;

        return mem;
    }

    inline void checked_free(void*& ptr){
        if(ptr)
            free(ptr);

        ptr = nullptr;
    }
}
//==========================================

namespace lel::core{
    template<typename T>
    struct Array{
        using SizeType = std::int32_t; // Must be signed because the scripting language has no unsigned integers
        using Iterator = T*;
        using ConstIterator = const T*;

        Array(SizeType initialSize = 0){
            assert(initialSize >= 0);

            data = checked_malloc(initialSize * sizeof(T) + sizeof(T));
            arraySize = initialSize;
            capacity = arraySize;

            init(0, size());
        }

        Array(const Array& other){ *this = other; }

        Array& operator=(const Array& other){
            resize(other.size());

            for(SizeType i = 0; i < size(); ++i)
                (*this)[i] = other[i];

            return *this;
        }

        ~Array(){
            deinit(0, size());
            checked_free(data);
        }

        SizeType size() const noexcept{ return arraySize; }
        T& operator[](SizeType index) noexcept{ return static_cast<T*>(data)[index]; }
        const T& operator[](SizeType index) const noexcept{ return static_cast<T*>(data)[index]; }

        void resize(SizeType newSize){
            if(newSize > 0){
                if(newSize <= arraySize){
                    arraySize = newSize;

                    deinit(newSize, arraySize - newSize);
                }else{
                    if(newSize <= capacity){
                        init(arraySize, newSize - arraySize);

                        arraySize = newSize;
                    }else{
                        auto newCapacity = static_cast<SizeType>(newSize * 1.25f + 1.0f);
                        Array tmp;

                        tmp.data = checked_malloc(newCapacity * sizeof(T) + sizeof(T));
                        tmp.arraySize = newSize;
                        tmp.capacity = newCapacity;

                        for(SizeType i = 0; i < size(); ++i)
                            new(&tmp[i]) T{(*this)[i]};

                        deinit(0, size());
                        checked_free(data);
                        data = tmp.data;
                        tmp.data = nullptr;
                        arraySize = tmp.size();
                        tmp.arraySize = 0;
                        capacity = tmp.capacity;
                        tmp.capacity = 0;
                    }
                }
            }else{
                deinit(0, size());

                arraySize = 0;
            }
        }

        void push_back(const T& t){
            resize(size() + 1);

            back() = t;
        }

        void clear(){
            deinit(0, size());

            checked_free(data);
            arraySize = 0;
            capacity = 0;
            data = checked_malloc(sizeof(T)); // For the end iterator
        }

        T& back() noexcept{ return (*this)[size() - 1]; }
        const T& back() const noexcept{ return (*this)[size() - 1]; }
        void pop_back(){ resize(size() - 1); }
        void emplace_back(){ push_back(T{}); }
        Iterator begin() noexcept{ return &(*this)[0]; }
        ConstIterator begin() const noexcept{ return &(*this)[0]; }
        Iterator end() noexcept{ return &(*this)[size()]; }
        ConstIterator end() const noexcept{ return &(*this)[size()]; }

    private:
        void* data = nullptr;
        SizeType arraySize = 0;
        SizeType capacity = 0;

        void init(SizeType startIndex, SizeType num){
            for(SizeType i = 0; i < num; ++i)
                new(&(*this)[startIndex + i]) T{};
        }

        void deinit(SizeType startIndex, SizeType num){
            if constexpr(!std::is_trivially_destructible<T>{}){
                for(SizeType i = 0; i < num; ++i)
                    std::destroy_at(&(*this)[startIndex + i]);
            }
        }

        static constexpr void static_assertions() noexcept{
            static_assert(std::is_standard_layout<Array<T>>{});
            static_assert(std::is_standard_layout<T>{});
        }
    };
}

```
\$\endgroup\$
  • \$\begingroup\$ You have a bug in your resize function. \$\endgroup\$ – Mike Borkland Feb 28 at 12:52
  • \$\begingroup\$ Hint: check what happens when newSize <= arraySize. \$\endgroup\$ – Mike Borkland Feb 28 at 12:59
  • \$\begingroup\$ @MikeBorkland You're right! Thanks a lot for pointing that out. It also made me realize that I don't even need the outermost if. \$\endgroup\$ – Leon2806 Feb 28 at 20:09

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.