Proper way to prevent deallocation using std::allocator and std::move - c++

As the title says, I wonder if this is a proper way to prevent deallocation in vector<T> a when moving vector<T> a tovector<T> b.
Vector header:
template<class T, class A = std::allocator<T>>
class vector
{
typedef typename A::size_type size_type;
A alloc;
T* start;
T* end;
public:
explicit vector(size_type n, const T& val = T(), const A& =A());
vector(vector&&);
~vector();
};
Constructor:
Note: allocate can throw std::bad_alloc.
template<class T, class A>
vector<T,A>::vector(size_type n, const T& val, const A& a)
: alloc(a)
{
start = alloc.allocate(n); //allocate memory for n elements.
end = start + n;
for(auto p = start; p!=end; p++)
alloc.construct(p, val); //construct val at allocated memory.
}
Move constructor:
Question: Is this a proper way to move vector v?
template<class T, class A>
vector<T,A>::vector(vector&& v)
:alloc(v.alloc) // copy the allocator in v.
{
start = std::move(v.start); // move the pointer previously returned by allocator.
end = std::move(v.end); // same boundary value.
v.start = v.end = nullptr; // nullptr to prevent deallocation when v is destroyed.
}
Destructor:
Question: According to cppreference, allocator::deallocate(p, n) deallocates memory from a pointer previously returned by allocator and n must be equal to the number of elements allocated for. What if this is not the case? My answer would be that allocator::deallocate does nothing if the pointer or the number of elements, n, is not equal to previous call to allocator::allocate(n). Is this true?
template<class T, class A>
vector<T, A>::~vector()
{
for(auto p = start; p!=end; p++)
alloc.destroy(p); //Destroy objects pointed to by alloc.
alloc.deallocate(start, end-start); //Deallocate memory.
}

Is this a proper way to move vector v?
Seems fine, but std::move is redundant with pointers. Also, you don't deallocate end, so you don't need to set it to null.
What if this is not the case? My answer would be that allocator::deallocate does nothing if the pointer or the number of elements, n, is not equal to previous call to allocator::allocate(n). Is this true?
No, the behaviour is undefined. You must pass the same n.

Related

C++ - Move assignment operator without custom swap function?

I'm reading Stroustrup's book (4th ed) and saw this example:
template<typename T, typename A = allocator<T>>
struct vector_base { // memory structure for vector
A alloc; // allocator
T* elem; // start of allocation
T* space; // end of element sequence, start of space allocated for possible expansion
T* last; // end of allocated space
vector_base(const A& a, typename A::size_type n, typename A::size_type m =0)
: alloc{a}, elem{alloc.allocate(n+m)}, space{elem+n}, last{elem+n+m} { }
~vector_base() { alloc.deallocate(elem,last-elem); }
vector_base(const vector_base&) = delete; // no copy operations
vector_base& operator=(const vector_base&) = delete;
vector_base(vector_base&&); // move operations
vector_base& operator=(vector_base&&);
};
template<typename T, typename A>
vector_base<T,A>::vector_base(vector_base&& a)
: alloc{a.alloc},
elem{a.elem},
space{a.space},
last{a.last}
{
a.elem = a.space = a.last = nullptr; // no longer owns any memory
}
template<typename T, typename A>
vector_base<T,A>& vector_base<T,A>::operator=(vector_base&& a)
{
swap(*this,a);
return *this;
}
From this answer, I understand that you can't just std::swap(*this, other) in an assignment operator because std::swap is often implemented in terms of the assignment operator itself. Therefore, you first need to provide your own custom swap function.
Is this a mistake, or am I missing something?
Looks like this is a duplicate of Usage of std::swap() inside move assignment should cause endless recursion (and causes), but it is an example from Stroustrup's book. Seems like Stroustrup does define a custom swap function; I seem to have missed it.
EDIT: I haven't found the swap function. Seems like it's a mistake on the book.

How to let a `std::vector<int32_t>` take memory from a `std::vector<uint32_t>&&`?

template<typename T>
struct raster {
std::vector<T> v;
template<typename U, typename = std::enable_if_t<sizeof(T) == sizeof(U)>>
raster(raster<U>&& other){
// What code goes here?
}
}
Suppose we have raster<uint32_t> r such that r.v.size() is in the millions, and a guarantee that all its elements are within int32_t's range. Is it possible for raster<int32_t>::raster(raster<uint32_t>&& other) to avoid copying the memory backing other.v?
Or should I just do something like *reinterpret_cast<raster<int32_t>*>(&r) instead of calling that constructor?
There is no legal way to do this in C++; you can only move buffers from a std::vector to another std::vector of the exact same type.
There are a variety of ways you can hack this. The most illegal and evil would be
std::vector<uint32_t> evil_steal_memory( std::vector<int32_t>&& in ) {
return reinterpret_cast< std::vector<uint32_t>&& >(in);
}
or something similar.
A less evil way would be to forget it is a std::vector at all.
template<class T>
struct buffer {
template<class A>
buffer( std::vector<T,A> vec ):
m_begin( vec.data() ),
m_end( m_begin + vec.size() )
{
m_state = std::unique_ptr<void, void(*)(void*)>(
new std::vector<T,A>( std::move(vec) ),
[](void* ptr){
delete static_cast<std::vector<T,A>*>(ptr);
}
);
}
buffer(buffer&&)=default;
buffer& operator=(buffer&&)=default;
~buffer() = default;
T* begin() const { return m_begin; }
T* end() const { return m_end; }
std::size_t size() const { return begin()==end(); }
bool empty() const { return size()==0; }
T* data() const { return m_begin; }
T& operator[](std::size_t i) const {
return data()[i];
}
explicit operator bool() const { return (bool)m_state; }
template<class U>
using is_buffer_compatible = std::integral_constant<bool,
sizeof(U)==sizeof(T)
&& alignof(U)==alignof(T)
&& !std::is_pointer<T>{}
>;
template<class U,
std::enable_if_t< is_buffer_compatible<U>{}, bool > = true
>
buffer reinterpret( buffer<U> o ) {
return {std::move(o.m_state), reinterpret_cast<T*>(o.m_begin()),reinterpret_cast<T*>(o.m_end())};
}
private:
buffer(std::unique_ptr<void, void(*)(void*)> state, T* b, T* e):
m_state(std::move(state)),
m_begin(begin),
m_end(end)
{}
std::unique_ptr<void, void(*)(void*)> m_state;
T* m_begin = 0;
T* m_end = 0;
};
live example: this type erases a buffer of T.
template<class T>
struct raster {
buffer<T> v;
template<typename U, typename = std::enable_if_t<sizeof(T) == sizeof(U)>>
raster(raster<U>&& other):
v( buffer<T>::reinterpret( std::move(other).v ) )
{}
};
note that my buffer has a memory allocation in it; compared to millions of elements that is cheap. It is also move-only.
The memory allocation can be eliminated through careful use of the small buffer optimization.
I'd leave it move-only (who wants to accidentally copy a million elements?) and maybe write
buffer clone() const;
which creates a new buffer with the same contents.
Note that instead of a const buffer<int> you should use a buffer<const int> under the above design. You can change that by duplicating the begin() const methods to have const and non-const versions.
This solution relies on your belief that reinterpreting a buffer of int32_ts as a buffer of uint32_ts (or vice versa) doesn't mess with anything. Your compiler may provide this guarantee, but the C++ standard does not.
The problem is that the implementation of the vector template itself may be specialised for the type. For some weird whacky reason we don't understand today, the top-level vector might need an extra member not provided in vector, so simply reinterpret casting will not work safely.
Another evil approach might be to look at the Allocator of your two vectors.
If this was
a custom allocator you had written and both were a derived class from vector
and you wrote an overload in the class for swap and =&&
within which you created wrapped tmp vectors to swap with
and detected that the Allocator was being called within those temporary objects constructors/destructors, and on the same thread
to release and reallocate the same sized array
Then, perhaps you could legitimately pass the memory buffer between them without resetting the content.
But that is a lot of fragile work!

Implementing a simple allocator class using malloc() and free()

Is this simple implementation of an allocator acceptable
template<typename T>
class Allocator
{
public:
T * allocate(int n); //allocate space for n objects of type T
void deallocate(T* p, int n); //deallocate n objects of type T starting at p
void construct(T* p, const T& v); //construct a T with the value v in p
void destroy(T* p); //destroy the T in p
};
template<typename T>
T* Allocator<T>::allocate(int n)
{
T* new_mem = (T*)malloc(n * sizeof(T));
return new_mem;
}
template<typename T>
void Allocator<T>::construct(T* p, const T& v)
{
T* constructed_object = new(p) T{ v };
}
template<typename T>
void Allocator<T>::deallocate(T* p, int n)
{
for (int i = 0; i < n; ++i)
{
free(&p[i]);
}
}
template<typename T>
void Allocator<T>::destroy(T* p)
{
p->~T();
}
I'm to use it in a vector to implement function that reserves exra space as follows:
template<typename T, typename A>
void vector<T, A>::reserve(int newalloc)
{
if (newalloc <= space)return;
T* p = alloc.allocate(newalloc);
for (int i = 0; i < sz; ++i)alloc.construct(&p[i], elem[i]);
for (int i = 0; i < sz; ++i)alloc.destroy(&elem[i]);
elem = p;
space = newalloc;
}
where typename A = Allocator<T> and alloc is of type A.
Will the allocator class I implemented be functional enough to work?
(I feel the deallocate function is suspect)
Your deallocation function is indeed incorrect. The rules for free and malloc is simple: you must pass exactly the pointers you got from malloc to free.
template<typename T>
void Allocator<T>::deallocate(T* p, size_t)
{
free(p);
}
Note you should in general pass the same pointer type to deallocation functions as well, but in this case since free only takes void* as parameter, the implicit conversion will take care of that.
No, this isn't going to work.
You ought to have a rebind method.
You need to provide comparison operators (== and !=)
Your allocate method allocates too much. By the time it gets to you, the sizeof(T) has already happened.
Your deallocate method is just wrong. You get a single pointer. The second parameter is a hint to the size of the allocation.
The construct and destruct methods are optional. If you don't provide them, they will be synthesized for you by allocator_traits.
See https://github.com/llvm-mirror/libcxx/blob/5a424a985612452d4f7a3f02422207456d54a53e/test/support/min_allocator.h#L21 for an example of a minimal allocator (albeit w/o a rebind )

Constructor from initializer_list

I am implementing a container in c++, a wrapper for an array in fact. I am not sure how to implement a constructor from initializer_list. I end up with this implementation but it seems to me really ugly. So could be an array allocated in a heap initialized by an initializer_list. Or is there an elegant way how to do it?
template <typename T> class sequence {
public:
sequence (size_t n): _data {new T[n]}, _size {n} {};
sequence (std::initializer_list<T> source);
~sequence() { delete[] _data; };
private:
pointer _data;
size_type _size;
};
//Initializer list constructor
template <class T> sequence<T>::sequence (std::initializer_list<T> source)
: sequence(source.size()) {
auto iterator = source.begin();
for ( int i=0; i < _size; i++) {
_data[i] = *iterator;
++iterator;
}
};
Depends on what you're trying to do.
If you're actually needing to use a sequence with bounded size, as determined at compile-time, then use std::array<T,std::size_t>, which is a wrapper around a C-style array (like what you are implementing), introduced in C++11.
However, as you said in one of your comments, you're doing this mostly for educational purposes. In that case, what you have is decent. The syntax could be cleaned up a little. Consider:
//Initializer list constructor
template <class T>
sequence<T>::sequence (std::initializer_list<T> source)
: sequence(source.size())
{
auto it = source.begin();
auto const e = source.cend();
auto d = data_;
while (it != e) {
*d++ = *it++;
}
};
That way you don't explicitly rely on the size(). You could consider making things more efficient by turning the it and e iterators into "move" iterators:
auto it = std::make_move_iterator(source.begin());
auto e = std::make_move_iterator(source.end());
That way, whenever it's dereferenced, its value is cast to an rvalue reference, allowing a move assignment.

Can I assume allocators don't hold their memory pool directly (and can therefore be copied)?

I'm writing a container and would like to permit the user to use custom allocators, but I can't tell if I should pass allocators around by reference or by value.
Is it guaranteed (or at least, a reasonable assumption to make) that an allocator object will not contain its memory pool directly, and hence it would be OK to copy an allocator and expect the memory pools of the allocators to be cross-compatible? Or do I always need to pass allocators by reference?
(I have found that passing by reference harms performance by a factor of > 2 because the compiler starts worrying about aliasing, so it makes a whether or not I can rely on this assumption.)
In C++11 section 17.6.3.5 Allocator requirements [allocator.requirements] specifies the requirements for conforming allocators. Among the requirements are:
X an Allocator class for type T
...
a, a1, a2 values of type X&
...
a1 == a2 bool returns true only if storage
allocated from each can be
deallocated via the other.
operator== shall be reflexive,
symmetric, and transitive, and
shall not exit via an exception.
...
X a1(a); Shall not exit via an exception.
post: a1 == a
I.e. when you copy an allocator, the two copies are required to be able to delete each other's pointers.
Conceivably one could put internal buffers into allocators, but copies would have to keep a list of other's buffers. Or perhaps an allocator could have an invariant that deallocation is always a no-op because the pointer always comes from an internal buffer (either from your own, or from some other copy).
But whatever the scheme, copies must be "cross-compatible".
Update
Here is a C++11 conforming allocator that does the "short string optimization". To make it C++11 conforming, I had to put the "internal" buffer external to the allocator so that copies are equal:
#include <cstddef>
template <std::size_t N>
class arena
{
static const std::size_t alignment = 16;
alignas(alignment) char buf_[N];
char* ptr_;
std::size_t
align_up(std::size_t n) {return n + (alignment-1) & ~(alignment-1);}
public:
arena() : ptr_(buf_) {}
arena(const arena&) = delete;
arena& operator=(const arena&) = delete;
char* allocate(std::size_t n)
{
n = align_up(n);
if (buf_ + N - ptr_ >= n)
{
char* r = ptr_;
ptr_ += n;
return r;
}
return static_cast<char*>(::operator new(n));
}
void deallocate(char* p, std::size_t n)
{
n = align_up(n);
if (buf_ <= p && p < buf_ + N)
{
if (p + n == ptr_)
ptr_ = p;
}
else
::operator delete(p);
}
};
template <class T, std::size_t N>
class stack_allocator
{
arena<N>& a_;
public:
typedef T value_type;
public:
template <class U> struct rebind {typedef stack_allocator<U, N> other;};
explicit stack_allocator(arena<N>& a) : a_(a) {}
template <class U>
stack_allocator(const stack_allocator<U, N>& a)
: a_(a.a_) {}
stack_allocator(const stack_allocator&) = default;
stack_allocator& operator=(const stack_allocator&) = delete;
T* allocate(std::size_t n)
{
return reinterpret_cast<T*>(a_.allocate(n*sizeof(T)));
}
void deallocate(T* p, std::size_t n)
{
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 stack_allocator<T1, N1>& x, const stack_allocator<U, M>& y);
template <class U, std::size_t M> friend class stack_allocator;
};
template <class T, std::size_t N, class U, std::size_t M>
bool
operator==(const stack_allocator<T, N>& x, const stack_allocator<U, M>& y)
{
return N == M && &x.a_ == &y.a_;
}
template <class T, std::size_t N, class U, std::size_t M>
bool
operator!=(const stack_allocator<T, N>& x, const stack_allocator<U, M>& y)
{
return !(x == y);
}
It could be used like this:
#include <vector>
template <class T, std::size_t N> using A = stack_allocator<T, N>;
template <class T, std::size_t N> using Vector = std::vector<T, stack_allocator<T, N>>;
int main()
{
const std::size_t N = 1024;
arena<N> a;
Vector<int, N> v{A<int, N>(a)};
v.reserve(100);
for (int i = 0; i < 100; ++i)
v.push_back(i);
Vector<int, N> v2 = std::move(v);
v = v2;
}
All allocations for the above problem are drawn from the local arena which is 1 Kb in size. You should be able to pass this allocator around by value or by reference.
The old C++ standard makes requirements for a standard-compliant allocator: These requirements include that if you have Alloc<T> a, b, then a == b, and you can use b to deallocate things that were allocated with a. Allocators are fundamentally stateless.
In C++11, the situation got a lot more involved, as there is now support for stateful allocators. As you copy and move objects around, there are specific rules whether one container can be copied or moved from another container if the allocators differ, and how the allocators get copied or moved.
Just to answer your question first: No, you can definitely not assume that it makes sense to copy your allocator around, and your allocator may not even be copyable.
Here is 23.2.1/7 on this subject:
Unless otherwise specified, all containers defined in this clause obtain memory using an allocator (see 17.6.3.5). Copy constructors for these container types obtain an allocator by calling allocator_traits<allocator_-type>::select_on_container_copy_construction on their first parameters. Move constructors obtain an allocator by move construction from the allocator belonging to the container being moved. Such move construction of the allocator shall not exit via an exception. All other constructors for these container types take an Allocator& argument (17.6.3.5), an allocator whose value type is the same as the container’s value type. [Note: If an invocation of a constructor uses the default value of an optional allocator argument, then the Allocator type must support value initialization. —end note] A copy of this allocator is used for any memory allocation performed, by these constructors and by all member functions, during the lifetime of each container object or until the allocator is replaced. The allocator may be replaced only via assignment or
swap(). Allocator replacement is performed by copy assignment, move assignment, or swapping of the allocator only if allocator_traits<allocator_type>::propagate_on_container_copy_assignment::value,
allocator_traits<allocator_type>::propagate_on_container_move_assignment::value, or allocator_traits<allocator_type>::propagate_on_container_swap::value is true within the implementation of the corresponding container operation. The behavior of a call to a container’s swap function is undefined unless the objects being swapped have allocators that compare equal or allocator_traits<allocator_type>::propagate_on_container_swap::value is true. In all container types defined in this Clause, the member get_allocator() returns a copy of the allocator used to construct the container or, if that allocator has been replaced, a copy of the most recent replacement.
See also the documentation of std::allocator_traits for a synopsis.