I am using boost pool as a static memory provider,
void func()
{
std::vector<int, boost::pool_allocator<int> > v;
for (int i = 0; i < 10000; ++i)
v.push_back(13);
}
In above code, how we can fix the size of pool, i mean as we know boost::pool provide as a static memory allocator, but i am not able to fix the size of this pool, its keep growing, there should be way to restrict its size.
for example i want a pool of 200 chunks only so i can take 200 chunks after that it should though NULL
please let me now how to do this
I don't think boost pool provides what you want. Actually there are 4 other template parameters for boost::pool_allocator except the type of object:
UserAllocator: Defines the method that the underlying Pool will use to allocate memory from the system(default = boost::default_user_allocator_new_delete).
Mutex: Allows the user to determine the type of synchronization to be used on the underlying singleton_pool(default = boost::details::pool::default_mutex).
NextSize: The value of this parameter is passed to the underlying Pool when it is created and specifies the number of chunks to allocate in the first allocation request (default = 32).
MaxSize: The value of this parameter is passed to the underlying Pool when it is created and specifies the maximum number of chunks to allocate in any single allocation request (default = 0).
You may think MaxSize is exactly what you want, but unfortunately it's not.
boost::pool_allocator uses a underlying boost::singleton_pool which is based on an boost::pool, the MaxSize will eventually pass to the data member of boost::pool<>: max_size, so what role does the max_size play in the boost::pool? let's have a look at boost::pool::malloc():
void * malloc BOOST_PREVENT_MACRO_SUBSTITUTION()
{ //! Allocates a chunk of memory. Searches in the list of memory blocks
//! for a block that has a free chunk, and returns that free chunk if found.
//! Otherwise, creates a new memory block, adds its free list to pool's free list,
//! \returns a free chunk from that block.
//! If a new memory block cannot be allocated, returns 0. Amortized O(1).
// Look for a non-empty storage
if (!store().empty())
return (store().malloc)();
return malloc_need_resize();
}
Obviously, boost::pool immediately allocates a new memory block if no free chunk available in the memory block. Let's continue to dig into the malloc_need_resize():
template <typename UserAllocator>
void * pool<UserAllocator>::malloc_need_resize()
{ //! No memory in any of our storages; make a new storage,
//! Allocates chunk in newly malloc aftert resize.
//! \returns pointer to chunk.
size_type partition_size = alloc_size();
size_type POD_size = static_cast<size_type>(next_size * partition_size +
math::static_lcm<sizeof(size_type), sizeof(void *)>::value + sizeof(size_type));
char * ptr = (UserAllocator::malloc)(POD_size);
if (ptr == 0)
{
if(next_size > 4)
{
next_size >>= 1;
partition_size = alloc_size();
POD_size = static_cast<size_type>(next_size * partition_size +
math::static_lcm<sizeof(size_type), sizeof(void *)>::value + sizeof(size_type));
ptr = (UserAllocator::malloc)(POD_size);
}
if(ptr == 0)
return 0;
}
const details::PODptr<size_type> node(ptr, POD_size);
BOOST_USING_STD_MIN();
if(!max_size)
next_size <<= 1;
else if( next_size*partition_size/requested_size < max_size)
next_size = min BOOST_PREVENT_MACRO_SUBSTITUTION(next_size << 1, max_size*requested_size/ partition_size);
// initialize it,
store().add_block(node.begin(), node.element_size(), partition_size);
// insert it into the list,
node.next(list);
list = node;
// and return a chunk from it.
return (store().malloc)();
}
As we can see from the source code, max_size is just related to the number of chunks to request from the system next time, we can only slow down the speed of increasing via this parameter.
But notice that we can defines the method that the underlying pool will use to allocate memory from the system, if we restrict the size of memory allocated from system, the pool's size wouldn't keep growing. In this way, boost::pool seems superfluous, you can pass the custom allocator to STL container directly. Here is a example of custom allocator(based on this link) which allocates memory from stack up to a given size:
#include <cassert>
#include <iostream>
#include <vector>
#include <new>
template <std::size_t N>
class arena
{
static const std::size_t alignment = 8;
alignas(alignment) char buf_[N];
char* ptr_;
bool
pointer_in_buffer(char* p) noexcept
{ return buf_ <= p && p <= buf_ + N; }
public:
arena() noexcept : ptr_(buf_) {}
~arena() { ptr_ = nullptr; }
arena(const arena&) = delete;
arena& operator=(const arena&) = delete;
char* allocate(std::size_t n);
void deallocate(char* p, std::size_t n) noexcept;
static constexpr std::size_t size() { return N; }
std::size_t used() const { return static_cast<std::size_t>(ptr_ - buf_); }
void reset() { ptr_ = buf_; }
};
template <std::size_t N>
char*
arena<N>::allocate(std::size_t n)
{
assert(pointer_in_buffer(ptr_) && "short_alloc has outlived arena");
if (buf_ + N - ptr_ >= n)
{
char* r = ptr_;
ptr_ += n;
return r;
}
std::cout << "no memory available!\n";
return NULL;
}
template <std::size_t N>
void
arena<N>::deallocate(char* p, std::size_t n) noexcept
{
assert(pointer_in_buffer(ptr_) && "short_alloc has outlived arena");
if (pointer_in_buffer(p))
{
if (p + n == ptr_)
ptr_ = p;
}
}
template <class T, std::size_t N>
class short_alloc
{
arena<N>& a_;
public:
typedef T value_type;
public:
template <class _Up> struct rebind { typedef short_alloc<_Up, N> other; };
short_alloc(arena<N>& a) noexcept : a_(a) {}
template <class U>
short_alloc(const short_alloc<U, N>& a) noexcept
: a_(a.a_) {}
short_alloc(const short_alloc&) = default;
short_alloc& operator=(const short_alloc&) = delete;
T* allocate(std::size_t n)
{
return reinterpret_cast<T*>(a_.allocate(n*sizeof(T)));
}
void deallocate(T* p, std::size_t n) noexcept
{
a_.deallocate(reinterpret_cast<char*>(p), n*sizeof(T));
}
template <class T1, std::size_t N1, class U, std::size_t M>
friend
bool
operator==(const short_alloc<T1, N1>& x, const short_alloc<U, M>& y) noexcept;
template <class U, std::size_t M> friend class short_alloc;
};
template <class T, std::size_t N, class U, std::size_t M>
inline
bool
operator==(const short_alloc<T, N>& x, const short_alloc<U, M>& y) noexcept
{
return N == M && &x.a_ == &y.a_;
}
template <class T, std::size_t N, class U, std::size_t M>
inline
bool
operator!=(const short_alloc<T, N>& x, const short_alloc<U, M>& y) noexcept
{
return !(x == y);
}
int main()
{
const unsigned N = 1024;
typedef short_alloc<int, N> Alloc;
typedef std::vector<int, Alloc> SmallVector;
arena<N> a;
SmallVector v{ Alloc(a) };
for (int i = 0; i < 400; ++i)
{
v.push_back(10);
}
}
Related
So I intend to use this stack-based allocator for std::vector, and I use 2 arrays for the allocation(because vectors grow and copy the old buffer to the new).
Here's the full code:
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <vector>
#include <cstddef>
#include <cassert>
#include <array>
using size_t = std::size_t;
using byte = std::byte;
template <class T, size_t capacity = 512>
class stack_allocator
{
public:
using value_type = T;
using pointer = value_type*;
using const_pointer = typename std::pointer_traits<pointer>::template rebind<value_type const>;
using void_pointer = typename std::pointer_traits<pointer>::template rebind<void>;
using const_void_pointer = typename std::pointer_traits<pointer>::template rebind<const void>;
using difference_type = typename std::pointer_traits<pointer>::difference_type;
using size_type = std::make_unsigned_t<difference_type>;
template <class U> struct rebind { typedef stack_allocator<U, capacity> other; };
stack_allocator() noexcept {}; // not required, unless used
~stack_allocator() noexcept = default;
//stack_allocator(stack_allocator&&) = delete;
//stack_allocator& operator=(stack_allocator&&) = delete;
//stack_allocator(const stack_allocator&) = delete;
//stack_allocator& operator=(const stack_allocator&) = delete;
template <class U>
stack_allocator(stack_allocator<U> const&) noexcept {}
// ? is n already aligned ?
inline pointer allocate(size_t n)
{
constexpr auto max_size_allowed = (capacity>>1);
auto size = n * sizeof(value_type);
if (size > max_size_allowed)
{
return static_cast<pointer>(::operator new (size));
}
else
{
m_Index = !m_Index;
return static_cast<pointer>(static_cast<void*>(&m_Array[static_cast<size_t>(m_Index)][0]));
}
}
inline void deallocate(pointer p, size_t n) noexcept
{
constexpr auto max_size_allowed = (capacity>>1);
auto size = n * sizeof(value_type);
if (size > max_size_allowed)
::operator delete(p);
else
{
// do nothing
}
}
inline pointer allocate(size_t n, const_void_pointer)
{
return allocate(n);
}
template <class U, class ...Args>
void construct(U* p, Args&& ...args)
{
::new(p) U(std::forward<Args>(args)...);
}
template <class U>
void destroy(U* p) noexcept
{
p->~U();
}
inline constexpr size_t max_size() const noexcept
{
return std::numeric_limits<std::size_t>::max() / sizeof(T);
}
stack_allocator select_on_container_copy_construction() const
{
return *this;
}
using propagate_on_container_copy_assignment = std::true_type;
using propagate_on_container_move_assignment = std::false_type;
using propagate_on_container_swap = std::true_type;
using is_always_equal = std::true_type;
protected:
//const bool is_pointer_in_range(byte* p) const noexcept { return (p >= &m_Array[0]) && (p <= &m_Array[capacity - 1]); }
bool m_Index{ false };
byte m_Array[2][(capacity >> 1)]{ {static_cast<byte>(0)} };
};
template <class T, size_t capacity, class U>
bool operator==(stack_allocator<T, capacity> const&, stack_allocator<U, capacity> const&) noexcept
{
return true;
}
template <class T, size_t capacity, class U>
bool operator!=(stack_allocator<T, capacity> const& x, stack_allocator<U, capacity> const& y) noexcept
{
return false;
}
int main(int argc, char** argv)
{
std::vector<int, stack_allocator<int, 512>> stackVec;
stackVec.push_back(1);
stackVec.push_back(2);
stackVec.push_back(3);
return 0;
}
Somehow the code cause crash:
enter image description here
enter image description here
I've notice that the "_Container_base12" inherits from the allocator, so I guess there must be something wrong with my "stack_allocator" implementation. But I can't figure it out.
Hope to know what is the correct way of writing an allocator.
Thanks to Igor Tandetnik, I was able to find out the issue. After some research, I also found out that, it is actually easy to do this in C++17, we could either do:
std::array<unsigned char, 64> memory;
std::pmr::monotonic_buffer_resource pool{ memory.data(), memory.size() };
// then we can define our vector using stack memory
std::vector<int, std::pmr::polymorphic_allocator<int>> vec{&pool};
or, we can put the "std::array<unsigned char, 64> memory" inside a custom memory_resource.
In my case, I want to use in-place array for small objects to avoid 'allocations'. And for large objects, I want to use pooled memory.
So I inherited from _Identity_equal_resource and use a global unsynchronized_pool_resource behind the scene.
Now everything is just looking perfect to me.
// Note: still testing this.
template<size_t local_arena_size>
struct LocalArenaMemoryResource : public std::pmr::_Identity_equal_resource
{
private:
static inline constexpr size_t max_blocks_per_chunk = 16;
static inline constexpr size_t largest_required_pool_block = 16 * 1024;
static inline std::pmr::unsynchronized_pool_resource _pool{ std::pmr::pool_options { max_blocks_per_chunk, largest_required_pool_block } };
private:
using base = std::pmr::unsynchronized_pool_resource;
void* do_allocate(size_t _Bytes, size_t _Align) override
{
if (_Bytes <= local_arena_size)
{
return static_cast<void*>(&m_local_buffer[0]);
}
return _pool.allocate(_Bytes, _Align);
}
void do_deallocate(void* _Ptr, size_t _Bytes, size_t _Align) override
{
if (_Ptr >= &m_local_buffer[0] && _Ptr < &m_local_buffer[local_arena_size - 1])
{
// do nothing
}
else
{
_pool.deallocate(_Ptr, _Bytes, _Align);
}
}
private:
std::array<byte, local_arena_size> m_local_buffer;
};
There are things I'm still learning, such as: how unsynchronized_pool_resource handles the allocation when oversize alloc is required. Also I'm wondering if a fixed_block_pool will be more efficient. Still learning, testing, but it's really fun.
I have custom array constructor like below:
rtc::ArrayView<const uint8_t> frame,
rtc::ArrayView<uint8_t> encrypted_frame,
uint8_t unencrypted_bytes = 10;
How I could use std::copy instead of for? or do I have another choice that have better performance instead of using for loop? if I am using this std::copy I would get an error "invalid operands to binary expression ('rtc::ArrayView' and 'uint8_t"
// for (size_t i = 0; i < unencrypted_bytes; i++) {
// encrypted_frame[i] = frame[i];
// frame_header.push_back(encrypted_frame[i]);
// RTC_LOG(LS_INFO) << "Ivan, unencrypted_bytes data: " << i << " "
// << encrypted_frame[i];
// }
// Copy Unencrypted Bytes
std::copy(frame, frame + unencrypted_bytes, encrypted_frame);
Below is my array view constructor
/*
* Copyright 2015 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef API_ARRAY_VIEW_H_
#define API_ARRAY_VIEW_H_
#include <algorithm>
#include <array>
#include <iterator>
#include <type_traits>
#include "rtc_base/checks.h"
#include "rtc_base/type_traits.h"
namespace rtc {
// tl;dr: rtc::ArrayView is the same thing as gsl::span from the Guideline
// Support Library.
//
// Many functions read from or write to arrays. The obvious way to do this is
// to use two arguments, a pointer to the first element and an element count:
//
// bool Contains17(const int* arr, size_t size) {
// for (size_t i = 0; i < size; ++i) {
// if (arr[i] == 17)
// return true;
// }
// return false;
// }
//
// This is flexible, since it doesn't matter how the array is stored (C array,
// std::vector, rtc::Buffer, ...), but it's error-prone because the caller has
// to correctly specify the array length:
//
// Contains17(arr, arraysize(arr)); // C array
// Contains17(arr.data(), arr.size()); // std::vector
// Contains17(arr, size); // pointer + size
// ...
//
// It's also kind of messy to have two separate arguments for what is
// conceptually a single thing.
//
// Enter rtc::ArrayView<T>. It contains a T pointer (to an array it doesn't
// own) and a count, and supports the basic things you'd expect, such as
// indexing and iteration. It allows us to write our function like this:
//
// bool Contains17(rtc::ArrayView<const int> arr) {
// for (auto e : arr) {
// if (e == 17)
// return true;
// }
// return false;
// }
//
// And even better, because a bunch of things will implicitly convert to
// ArrayView, we can call it like this:
//
// Contains17(arr); // C array
// Contains17(arr); // std::vector
// Contains17(rtc::ArrayView<int>(arr, size)); // pointer + size
// Contains17(nullptr); // nullptr -> empty ArrayView
// ...
//
// ArrayView<T> stores both a pointer and a size, but you may also use
// ArrayView<T, N>, which has a size that's fixed at compile time (which means
// it only has to store the pointer).
//
// One important point is that ArrayView<T> and ArrayView<const T> are
// different types, which allow and don't allow mutation of the array elements,
// respectively. The implicit conversions work just like you'd hope, so that
// e.g. vector<int> will convert to either ArrayView<int> or ArrayView<const
// int>, but const vector<int> will convert only to ArrayView<const int>.
// (ArrayView itself can be the source type in such conversions, so
// ArrayView<int> will convert to ArrayView<const int>.)
//
// Note: ArrayView is tiny (just a pointer and a count if variable-sized, just
// a pointer if fix-sized) and trivially copyable, so it's probably cheaper to
// pass it by value than by const reference.
namespace impl {
// Magic constant for indicating that the size of an ArrayView is variable
// instead of fixed.
enum : std::ptrdiff_t { kArrayViewVarSize = -4711 };
// Base class for ArrayViews of fixed nonzero size.
template <typename T, std::ptrdiff_t Size>
class ArrayViewBase {
static_assert(Size > 0, "ArrayView size must be variable or non-negative");
public:
ArrayViewBase(T* data, size_t size) : data_(data) {}
static constexpr size_t size() { return Size; }
static constexpr bool empty() { return false; }
T* data() const { return data_; }
protected:
static constexpr bool fixed_size() { return true; }
private:
T* data_;
};
// Specialized base class for ArrayViews of fixed zero size.
template <typename T>
class ArrayViewBase<T, 0> {
public:
explicit ArrayViewBase(T* data, size_t size) {}
static constexpr size_t size() { return 0; }
static constexpr bool empty() { return true; }
T* data() const { return nullptr; }
protected:
static constexpr bool fixed_size() { return true; }
};
// Specialized base class for ArrayViews of variable size.
template <typename T>
class ArrayViewBase<T, impl::kArrayViewVarSize> {
public:
ArrayViewBase(T* data, size_t size)
: data_(size == 0 ? nullptr : data), size_(size) {}
size_t size() const { return size_; }
bool empty() const { return size_ == 0; }
T* data() const { return data_; }
protected:
static constexpr bool fixed_size() { return false; }
private:
T* data_;
size_t size_;
};
} // namespace impl
template <typename T, std::ptrdiff_t Size = impl::kArrayViewVarSize>
class ArrayView final : public impl::ArrayViewBase<T, Size> {
public:
using value_type = T;
using const_iterator = const T*;
// Construct an ArrayView from a pointer and a length.
template <typename U>
ArrayView(U* data, size_t size)
: impl::ArrayViewBase<T, Size>::ArrayViewBase(data, size) {
RTC_DCHECK_EQ(size == 0 ? nullptr : data, this->data());
RTC_DCHECK_EQ(size, this->size());
RTC_DCHECK_EQ(!this->data(),
this->size() == 0); // data is null iff size == 0.
}
// Construct an empty ArrayView. Note that fixed-size ArrayViews of size > 0
// cannot be empty.
ArrayView() : ArrayView(nullptr, 0) {}
ArrayView(std::nullptr_t) // NOLINT
: ArrayView() {}
ArrayView(std::nullptr_t, size_t size)
: ArrayView(static_cast<T*>(nullptr), size) {
static_assert(Size == 0 || Size == impl::kArrayViewVarSize, "");
RTC_DCHECK_EQ(0, size);
}
// Construct an ArrayView from a C-style array.
template <typename U, size_t N>
ArrayView(U (&array)[N]) // NOLINT
: ArrayView(array, N) {
static_assert(Size == N || Size == impl::kArrayViewVarSize,
"Array size must match ArrayView size");
}
// (Only if size is fixed.) Construct a fixed size ArrayView<T, N> from a
// non-const std::array instance. For an ArrayView with variable size, the
// used ctor is ArrayView(U& u) instead.
template <typename U,
size_t N,
typename std::enable_if<
Size == static_cast<std::ptrdiff_t>(N)>::type* = nullptr>
ArrayView(std::array<U, N>& u) // NOLINT
: ArrayView(u.data(), u.size()) {}
// (Only if size is fixed.) Construct a fixed size ArrayView<T, N> where T is
// const from a const(expr) std::array instance. For an ArrayView with
// variable size, the used ctor is ArrayView(U& u) instead.
template <typename U,
size_t N,
typename std::enable_if<
Size == static_cast<std::ptrdiff_t>(N)>::type* = nullptr>
ArrayView(const std::array<U, N>& u) // NOLINT
: ArrayView(u.data(), u.size()) {}
// (Only if size is fixed.) Construct an ArrayView from any type U that has a
// static constexpr size() method whose return value is equal to Size, and a
// data() method whose return value converts implicitly to T*. In particular,
// this means we allow conversion from ArrayView<T, N> to ArrayView<const T,
// N>, but not the other way around. We also don't allow conversion from
// ArrayView<T> to ArrayView<T, N>, or from ArrayView<T, M> to ArrayView<T,
// N> when M != N.
template <
typename U,
typename std::enable_if<Size != impl::kArrayViewVarSize &&
HasDataAndSize<U, T>::value>::type* = nullptr>
ArrayView(U& u) // NOLINT
: ArrayView(u.data(), u.size()) {
static_assert(U::size() == Size, "Sizes must match exactly");
}
template <
typename U,
typename std::enable_if<Size != impl::kArrayViewVarSize &&
HasDataAndSize<U, T>::value>::type* = nullptr>
ArrayView(const U& u) // NOLINT(runtime/explicit)
: ArrayView(u.data(), u.size()) {
static_assert(U::size() == Size, "Sizes must match exactly");
}
// (Only if size is variable.) Construct an ArrayView from any type U that
// has a size() method whose return value converts implicitly to size_t, and
// a data() method whose return value converts implicitly to T*. In
// particular, this means we allow conversion from ArrayView<T> to
// ArrayView<const T>, but not the other way around. Other allowed
// conversions include
// ArrayView<T, N> to ArrayView<T> or ArrayView<const T>,
// std::vector<T> to ArrayView<T> or ArrayView<const T>,
// const std::vector<T> to ArrayView<const T>,
// rtc::Buffer to ArrayView<uint8_t> or ArrayView<const uint8_t>, and
// const rtc::Buffer to ArrayView<const uint8_t>.
template <
typename U,
typename std::enable_if<Size == impl::kArrayViewVarSize &&
HasDataAndSize<U, T>::value>::type* = nullptr>
ArrayView(U& u) // NOLINT
: ArrayView(u.data(), u.size()) {}
template <
typename U,
typename std::enable_if<Size == impl::kArrayViewVarSize &&
HasDataAndSize<U, T>::value>::type* = nullptr>
ArrayView(const U& u) // NOLINT(runtime/explicit)
: ArrayView(u.data(), u.size()) {}
// Indexing and iteration. These allow mutation even if the ArrayView is
// const, because the ArrayView doesn't own the array. (To prevent mutation,
// use a const element type.)
T& operator[](size_t idx) const {
RTC_DCHECK_LT(idx, this->size());
RTC_DCHECK(this->data());
return this->data()[idx];
}
T* begin() const { return this->data(); }
T* end() const { return this->data() + this->size(); }
const T* cbegin() const { return this->data(); }
const T* cend() const { return this->data() + this->size(); }
std::reverse_iterator<T*> rbegin() const {
return std::make_reverse_iterator(end());
}
std::reverse_iterator<T*> rend() const {
return std::make_reverse_iterator(begin());
}
std::reverse_iterator<const T*> crbegin() const {
return std::make_reverse_iterator(cend());
}
std::reverse_iterator<const T*> crend() const {
return std::make_reverse_iterator(cbegin());
}
ArrayView<T> subview(size_t offset, size_t size) const {
return offset < this->size()
? ArrayView<T>(this->data() + offset,
std::min(size, this->size() - offset))
: ArrayView<T>();
}
ArrayView<T> subview(size_t offset) const {
return subview(offset, this->size());
}
};
// Comparing two ArrayViews compares their (pointer,size) pairs; it does *not*
// dereference the pointers.
template <typename T, std::ptrdiff_t Size1, std::ptrdiff_t Size2>
bool operator==(const ArrayView<T, Size1>& a, const ArrayView<T, Size2>& b) {
return a.data() == b.data() && a.size() == b.size();
}
template <typename T, std::ptrdiff_t Size1, std::ptrdiff_t Size2>
bool operator!=(const ArrayView<T, Size1>& a, const ArrayView<T, Size2>& b) {
return !(a == b);
}
// Variable-size ArrayViews are the size of two pointers; fixed-size ArrayViews
// are the size of one pointer. (And as a special case, fixed-size ArrayViews
// of size 0 require no storage.)
static_assert(sizeof(ArrayView<int>) == 2 * sizeof(int*), "");
static_assert(sizeof(ArrayView<int, 17>) == sizeof(int*), "");
static_assert(std::is_empty<ArrayView<int, 0>>::value, "");
template <typename T>
inline ArrayView<T> MakeArrayView(T* data, size_t size) {
return ArrayView<T>(data, size);
}
// Only for primitive types that have the same size and aligment.
// Allow reinterpret cast of the array view to another primitive type of the
// same size.
// Template arguments order is (U, T, Size) to allow deduction of the template
// arguments in client calls: reinterpret_array_view<target_type>(array_view).
template <typename U, typename T, std::ptrdiff_t Size>
inline ArrayView<U, Size> reinterpret_array_view(ArrayView<T, Size> view) {
static_assert(sizeof(U) == sizeof(T) && alignof(U) == alignof(T),
"ArrayView reinterpret_cast is only supported for casting "
"between views that represent the same chunk of memory.");
static_assert(
std::is_fundamental<T>::value && std::is_fundamental<U>::value,
"ArrayView reinterpret_cast is only supported for casting between "
"fundamental types.");
return ArrayView<U, Size>(reinterpret_cast<U*>(view.data()), view.size());
}
} // namespace rtc
#endif // API_ARRAY_VIEW_H_
Your ArrayView class supports the standard iterator interface of C++ containers. It does not implicitly convert to a pointer like you are trying to do. You should use its .begin() member function to build the range you are trying to copy:
std::copy(frame.begin(), frame.begin() + unencrypted_bytes, encrypted_frame.begin());
I definitely suggest reading up on C++ iterators to understand this better if you have not encountered them before or are coming from C.
I have a custom implementation for arrays with arbitrary upper and lower bounds. Now I want to be able to freely convert between arrays of the same type, as long as they have the same length. In code it looks like this:
template<typename T, int L, int H>
class Array{
public:
// stuff ...
operator Array<T, int lb, int hb>& () {
auto converted = Array<T, lb, hb>;
converted.actualArray = actualArray;
converted.offset = lb;
return *converted;
}
private:
T actualArray[H - L + 1];
int offset = 0 - L;
}
As you can see, the class needs to convert to itself. As you can probably also see, I am quite the noob at C++, as the error I'm given appears to be a syntax one:
wrong number of template arguments (2, should be 3)
operator Array<T, int lb, int hb>& () {
^
'<expression error>' does not name a type
operator Array<T, int lb, int hb>& () {
^
What am I doing wrong, that my operator's return type is not recognized? I really hope it's not a simple typo, that would be stupid.
You need a second set of template parameters for the operator itself, so it can be called with different template values than the main class uses.
Even if you could get the template parameters right, it still wouldn't work since you can't assign a raw array to another raw array. You need to copy the elements instead, such as with std:copy() or std::copy_n().
Try something more like this:
#include <algorithm>
template<typename T, size_t L, size_t H>
class Array
{
public:
static_assert(H >= L);
static const size_t Low = L;
static const size_t High = H;
static const size_t Length = (H - L + 1);
// stuff ...
template<size_t Lb, size_t Hb>
operator Array<T, Lb, Hb>() const
{
static_assert(Length == Array<T, Lb, Hb>::Length);
Array<T, Lb, Hb> converted;
std::copy_n(actualArray, Length, converted.actualArray);
return converted;
}
// just to show that you don't need an offset member...
T& operator[](size_t idx)
{
return actualArray[idx - L];
}
T operator[](size_t idx) const
{
return actualArray[idx - L];
}
template<typename, size_t, size_t>
friend class Array;
private:
T actualArray[Length];
};
Live Demo
Alternative, you could define a copy constructor that accepts multiple Array types of the same array size, and then you don't need the conversion operator anymore:
#include <algorithm>
template<typename T, size_t L, size_t H>
class Array
{
public:
static_assert(H >= L);
static const size_t Low = L;
static const size_t High = H;
static const size_t Length = (H - L + 1);
// stuff ...
Array() = default;
template<size_t Lb, size_t Hb>
Array(const Array<T, Lb, Hb> &src)
{
static_assert(Length == Array<T, Lb, Hb>::Length);
std::copy_n(src.actualArray, Length, actualArray);
}
// just to show that you don't need an offset member...
T& operator[](size_t idx)
{
return actualArray[idx - L];
}
T operator[](size_t idx) const
{
return actualArray[idx - L];
}
template<typename, size_t, size_t>
friend class Array;
private:
T actualArray[Length];
};
Live Demo
For a school assignment I want to build a custom array container as I'm not allowed to use containers provided by std, or any library available, while self made is allowed.
So far everything I have is working but I want to double my array size as soon as I reach the limit. how can i do this, all i can find is using vector (which i'm not allowed).
#ifndef UTILS_ARRAY_HEADER_INCLUDED
#define UTILS_ARRAY_HEADER_INCLUDED
#include <array>
namespace utils
{
template <class T>
struct Array
{
private:
int count = 0;
int size = 1;
std::array<T, 1> myArray;
void doubleSize();
public:
T* begin();
T* end();
T& operator[] (int);
void addItem(T const);
};
template <class T>
T* Array<T>::begin()
{
return &myArray[0];
}
template <class T>
T* Array<T>::end()
{
if (&myArray[count])
return &myArray[count];
return &myArray[0];
}
template <class T>
T& Array<T>::operator[] (int key)
{
return myArray[key];
}
template <class T>
void Array<T>::addItem(T const item)
{
if (count >= 0 && count < size)
{
myArray[count] = item;
count++;
}
else {
doubleSize();
}
return;
}
template <class T>
void Array<T>::doubleSize()
{
// ?
/*size = size * 2;
const int newsize = 2;
std::array<T, newsize> newArray; // not working.
std::copy(std::begin(myArray), std::end(myArray), std::begin(newArray));
myArray = newArray;*/
}
}
#endif
You need properties:
current capacity: int
current size (max used index): int
pointer to your data: T *
In AddItem check if current_size < current_capacity. If yes,
create new_data with size of currernt_capacity * 2, copy each item, delete old data and replace pointer.
Remember to do delete data; in destructor. I won't give you more code, it's your homework.
Checkout valgrind to check if your code does not leak memory.
I suspect that std::array<T, 1> is not allowed either. But that's not a real problem: you can't use array anyway since it has a fixed size.
You'll need to store a T* begin, a size_t current_size and a size_t size_in_use.
I often work with multi-dimensional arrays, and I am not a big fan of std::vector, since it is not possible to instantiate a std::vector or a std::vector of std::vector's using a reference without copying the underlying data.
For one-dimensional arrays, I use the following
template<typename T>
using deleted_aligned_array = std::unique_ptr<T[], std::function<void(T*)> >;
template<typename T>
deleted_aligned_array<T> deleted_aligned_array_create(size_t n) {
return deleted_aligned_array<T>((T*)_mm_malloc(n*sizeof(T),16), [](T* f)->void { _mm_free(f);});
}
This is very convenient and allows me to instantiate a dynamically sized array, which also works for a size of zero. Further, I can use std::forward to pass on the data without copying.
For a two-dimensional array, I would like to do something like
template<typename T>
using deleted_aligned_array2 = std::unique_ptr<T*,std::function<void(T**)>>;
template<typename T>
deleted_aligned_array2<T> deleted_aligned_array_create(size_t m, size_t n) {
auto arr = deleted_aligned_array2(new T*[m](), [&](T** x) {
if (malloc_usable_size(x) > 0) {
_mm_free(&(x[0][0]));
}
delete[] x;});
if (m*n > 0) {
arr.get()[0] = (T*) _mm_malloc(m*n*sizeof(T),16);
// Row pointers
for (size_t iRow = 1; iRow < m; iRow++) {
(m_data.get())[iRow] = &(m_data.get()[0][iRow*n]);
}
}
return arr;
}
It works for zero-size arrays, but I get an error from valgrind for obvious reasons, invalid read of size 8.
Is it possible to solve this in an elegant way, without creating an entire class keeping a std::unique_ptr member, where I implement move-constructor, move-assignment etc. Ultimately, I would like to generalize this to be used for any dimension
template<typename T, size_t Dim>
deleted_aligned_array<T,D> deleted_aligned_array_create(...);
The returned array should be a unique pointer with row pointer recursively initialized and it should support zero-size arrays, e.g.
auto arr = deleted_aligned_array_create<float,3>(4,5,10);
should return a 3-dimensional array with row and column pointers.
Issues:
1) Avoid reading invalid data in a simple way.
2) Use a template parameter D for generating the types: T*, T** and simply passing on D to code recursively generating row pointers (this I already have).
3) Preferably in a portable way. malloc_usable_size is a GNU extension and calling it on x results in an invalid read, when the size is 0.
Thanks in advance
I sort of found a solution but it is not very elegant. If you have a more elegant solution, please post your answer. The solution here is pretty ugly, once we get to higher dimensions.
template <class T, size_t D>
class deleted_aligned_multi_array {
};
template <class T>
class deleted_aligned_multi_array<T,1> : public std::unique_ptr<T[], std::function<void(T*)> > {
deleted_aligned_multi_array(size_t n) :
std::unique_ptr<T[], std::function<void(T*)> >((T*)_mm_malloc(n*sizeof(T),16),
[](T* f)->void { _mm_free(f);}) {}
};
template <class T>
class deleted_aligned_multi_array<T,2> {
public:
typedef T** pointer;
typedef std::unique_ptr<T*, std::function<void(T**)>> deleted_unique_array;
deleted_aligned_multi_array() : m(0), n(0), data() {}
deleted_aligned_multi_array(size_t m, size_t n) : m(m), n(n) {
if (m*n > 0) {
data = deleted_unique_array(new T*[m](),
[&](T** x) {
if (sps::msize(x) > 0) {
_mm_free(&(x[0][0]));
}
delete[] x;});
data.get()[0] = (T*) _mm_malloc(m*n*sizeof(T),16);
for (size_t iRow = 1; iRow < m; iRow++) {
(data.get())[iRow] = &(data.get()[0][iRow*n]);
}
}
else {
data.reset();
}
}
deleted_aligned_multi_array(deleted_aligned_multi_array&& other) : m(other.m), n(other.n),
data(std::move(other.data)) {}
deleted_aligned_multi_array& operator=( deleted_aligned_multi_array&& other ) {
if (this != &other) {
data = std::move( other.data );
m = other.m;
m = other.n;
}
return *this;
}
T& operator()(size_t i, size_t j) {
return this->data.get()[0][i*n + j];
}
T* operator[](size_t m) {
return &(data.get()[m][0]);
}
const T* operator[](size_t m) const {
return data.get()[m];
}
pointer get() const {
return data.get();
}
void reset(pointer __p = pointer()) {
data.reset(__p);
}
template<typename _Up>
void reset(_Up) = delete;
private:
deleted_aligned_multi_array(const deleted_aligned_multi_array& other) = delete;
deleted_aligned_multi_array& operator=( const deleted_aligned_multi_array& a ) = delete;
public:
size_t m; ///<Number of rows
size_t n; ///<Number of columns
deleted_unique_array data; ///<Data
};
A utility function for accessing a sub array, can now easily be made
template <class T>
std::unique_ptr<T*, std::function<void(T*)> sub_array(size_t m, size_t n, size_t i, size_t j) {
// Establish row pointers with reference i and j and dimension mxn.
}