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.
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.
Suppose the following multi_array structure:
template <typename type, std::size_t... sizes>
struct multi_array
{
using storage_type = typename storage_type<type, sizes...>::type;
using value_type = type;
using size_type = std::array<std::size_t , sizeof...(sizes)>;
using difference_type = std::array<std::ptrdiff_t, sizeof...(sizes)>;
using reference = value_type&;
using const_reference = const value_type&;
// ...
storage_type data_ {};
size_type sizes_ {sizes...};
}
// Example usage:
// multi_array<float, 3, 4, 5> three_dimensional_float_array;
// storage_type will be std::array<std::array<std::array<float, 5>, 4>, 3>
// size_type will be std::array<std::size_t, 3>
where:
// Helpers to create a std::array<std::array<...>> from a pack expansion.
template <typename type, std::size_t... sizes>
struct storage_type;
template <typename _type, std::size_t size, std::size_t... sizes>
struct storage_type<_type, size, sizes...>
{
using type = std::array<typename storage_type<_type, sizes...>::type, size>;
};
template <typename _type>
struct storage_type<_type>
{
using type = _type;
};
I am now trying to implement the .at() function:
[[nodiscard]]
constexpr reference at (const size_type& position)
{
// This has to be:
// data_[position[0]][position[1]][position[2]]...;
}
Fold expressions do not work in this case so a recursive solution is necessary.
I think this is the way, but can't seem to come up with the right answer:
[[nodiscard]]
constexpr reference at(const size_type& position)
{
return access_type<value_type, sizeof...(sizes)>::at(data_, position);
}
template <typename type, std::size_t size, std::size_t index = 0>
struct access_type
{
template <typename array_type>
auto at (const array_type& array, const std::array<std::size_t, size>& position, type& current) // Problem: Reference is not a type& for intermediates.
{
if constexpr (index == 0)
current = &array;
if constexpr (index + 1 != size)
{
return access_type::at<type, size, index + 1>(array, position, current);
}
}
};
Something along these lines, perhaps:
template <size_t level, size_t max_level>
struct Access {
template<typename Store>
static reference At(Store& store, const size_type& position) {
return Access<level + 1, max_level>::At(
store[position[level]], position);
}
};
template <size_t level>
struct Access<level, level> {
template<typename Store>
static reference At(Store& store, const size_type&) {
return store;
}
};
reference at(const size_type& position)
{
return Access<0, sizeof...(sizes)>::At(data_, position);
}
Here is my take:
namespace _details
{
template<class InputIt, class OutputIt>
OutputIt partial_product(InputIt first, InputIt last, OutputIt output)
{ *output++ = 1; return partial_sum(first, last, output, std::multiplies<>{}); }
// cache-friendly:
// neighbor objects within the right-most coordinate are neighbors in memory
template<class TDim, class TCoord>
auto coordinates_to_index(TDim const& dimensions, TCoord const& coords)
{
std::array<std::size_t, dimensions.size()> dimension_product;
using std::crbegin, std::crend, std::prev;
partial_product(crbegin(dimensions), prev(crend(dimensions)), begin(dimension_product));
return std::inner_product(cbegin(dimension_product), cend(dimension_product), crbegin(coords), 0);
}
}
It transforms tuple (x,y,z,...) into 1-D index x*N + y*K + ..., where N, K, ... are chosen accordingly to the total container dimensions.
And used in my own multi-dimension cache-friendly container:
/**
* #brief Returns a reference to the element at coordinates.
* #param coordinates Coordinates of the element to return
*
* No bounds checking is performed; if #c coordinates are outside od
* the matrix dimensions, the behavior is undefined.
*/
template<class... Args>
T const& operator()(Args... coordinates) const
{ return _data[_details::coordinates_to_index(dimensions, std::array{coordinates...})]; }
Source : yscialom/matrix/matrix.hpp on my GitHub (PR welcomed)
I have some shared memory populated by specialized hardware. It's declared as an array of structs, like:
struct port {
int data[10];
char port_id[8];
}
struct bus {
port ports[5];
char bus_id[8];
}
struct bus busses[10];
I'm (re)learning C++, and wanted to use C++11's ranged for loops to iterate over the data.
HOWEVER: That last dimension of the array (data[10]), I only care about the first 4 elements. Is there a way to take a slice of the data and use it in the for() statement?
Like
for (auto & b : busses) {
for (auto & p : bus.ports) {
for (auto & d : port.data[0 through 3]) {
store_the_address_of_d_for_use_elsewhere(d);
}
}
}
Is there a way to use a cast in the innermost for loop, so that it appears like there's only 4 elements? The address of the data is important because I'm going to refer directly to it later using pointers.
This is probably one of those times when a good old-fashioned for (int i = 0; i < 4; i++) is your best bet.
Don't overthink it, and don't try to use "new" features just for the sake of it, creating more complexity and more work in the process.
template<class T>
struct array_view {
T* b = 0;
T* e = 0;
T* begin() const { return b; }
T* end() const { return e; }
std::size_t size() const { return end()-begin(); }
T& front() const { return *begin(); }
T& back() const { return *(end()-1); }
// basic constructors:
array_view(T* s, T* f):b(s), e(f) {}
array_view(T* s, std::size_t N):array_view(s, s+N) {}
// default ctors: (no need for move)
array_view()=default;
array_view(array_view const&)=default;
array_view& operator=(array_view const&)=default;
// advanced constructors:
template<class U>
using is_compatible = std::integral_constant<bool,
std::is_same<U, T*>{} || std::is_same<U, T const*>{} ||
std::is_same<U, T volatile*>{} || std::is_same<U, T volatile const*>{}
>;
// this one consumes containers with a compatible .data():
template<class C,
typename std::enable_if<is_compatible< decltype(std::declval<C&>().data()) >{}, int>::type = 0
>
array_view( C&& c ): array_view( c.data(), c.size() ) {}
// this one consumes compatible arrays:
template<class U, std::size_t N,
typename std::enable_if<is_compatible< U* >{}, int>::type = 0
>
array_view( U(&arr)[N] ):
array_view( arr, N )
{}
// create a modified view:
array_view without_front( std::size_t N = 1 ) const {
return {begin()+(std::min)(size(), N), end()};
}
array_view without_back( std::size_t N = 1 ) const {
return {begin(), end()-(std::min)(size(), N)};
}
array_view only_front( std::size_t N = 1 ) const {
return {begin(), begin()+(std::min)(size(), N)};
}
array_view only_back( std::size_t N = 1 ) const {
return {end()-(std::min)(size(), N), end()};
}
};
Now some functions that let you easily create it:
template<class T, std::size_t N>
array_view<T> array_view_of( T(&arr)[N] ) {
return arr;
}
template<class C,
class Data = decltype( std::declval<C&>().data() ),
class T = typename std::remove_pointer<Data>::type
>
array_view<T> array_view_of( C&& c ) {
return std::forward<C>(c);
}
template<class T>
array_view<T> array_view_of( T* s, std::size_t N ) {
return {s, N};
}
template<class T>
array_view<T> array_view_of( T* s, T* e ) {
return {s, e};
}
and we are done the boilerplate part.
for (auto & b : bus) {
for (auto & p : bus.port) {
for (auto & d : array_view_of(bus.data).only_front(4)) {
store_the_address_of_d_for_use_elsewhere(d);
}
}
}
live example
Now I would only advocate this approach because array_view is shockingly useful in many different applications. Writing it just for this case is silly.
Note that the above array_view is a multiply-iterated class; I've written it here before. This one is, in my opinion, better than the previous ones, other than the annoying c++11-isms I had to use.
for (auto & d : reinterpret_cast<int (&)[4]>(p))
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.
}
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);
}
}