I tried to create a custom memory allocator which uses a smart pointer. I do not post the code because it's too big and doesn't add much of information. Then I tested it with a std::vector
. It works perfectly well on Xcode. But when I tried to build the same code in Visual Studio 12 (2013), the build failed with the following error:
...vector(873): error C2660: '
std::_Wrap_alloc< my_allocator< int > >::construct
' : function does not take 2 arguments
the problem is in push_back method:
void push_back(value_type&& _Val)
{
....
this->_Getal().construct(this->_Mylast,
_STD forward<value_type>(this->_Myfirst[_Idx]));
....
}
The error message is a bit confusing. Real problem is that this->_Mylast
is of type my_allocator< int >::pointer
, which is a smart pointer, and construct method expects int*
.
So, the question is simple: what are the requirements to pointer types used in a custom memory allocator? Should X::pointer
be convertible to a raw pointer? If yes, it makes them pretty useless.
Actually I would expect that line of code to look like:
this->_Getal().construct(addressof(*(this->_Mylast)),
_STD forward<value_type>(this->_Myfirst[_Idx]));
Let's try to find an answer in C++ standard, which says:
[17.6.3.5-5] An allocator type X shall satisfy the requirements of CopyConstructible (17.6.3.1). The
X::pointer
,X::const_pointer
,X::void_pointer
, andX::const_void_pointer
types shall satisfy the requirements of NullablePointer (17.6.3.3). No constructor, comparison operator, copy operation, move operation, or swap operation on these types shall exit via an exception.X::pointer
andX::const_pointer
shall also satisfy the requirements for a random access iterator (24.2)
If we take a look at NullablePointer reqs, they add few other requirements:
[17.6.3.3] A NullablePointer type is a pointer-like type that supports null values. A type P meets the requirements of NullablePointer if:
(1.1) — P satisfies the requirements of EqualityComparable, DefaultConstructible, CopyConstructible, CopyAssignable, and Destructible...
If I check random access iterator requirements, I also don't find any explicit mentioning of its casting to a raw pointer. But in few places the approach with addressof
is used (e. g. 24.2.1-5).
Also, it's not the only place in Microsoft's std::vector
implementation where X::pointer
and raw pointer are assumed to be equal. I'm wondering, what do I miss?
EDIT: I'll add a piece of my_allocator deffinition here:
class my_allocator
{
public:
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
typedef my_ptr<T> pointer;
typedef my_ptr<const T> const_pointer;
typedef T& reference;
typedef const T& const_reference;
typedef T value_type;
typedef my_ptr<void> void_pointer;
typedef my_ptr<const void> const_void_pointer;
<constructors>
pointer allocate(size_type n, const_void_pointer = nullptr);
void deallocate(const pointer& ptr, size_type elements_num);
};
This custom memory allocator is optimized for the allocation of small objects. It maintains chains of blocks that are the same size; the number of chains and maximum size of allocated object can be customized.
We use the term custom memory allocation in a proscribed way to denote any memory allocation mechanism that differs from general- purpose allocation in at least one of two ways. First, a custom allo- cator may provide more than one object for every allocated chunk of memory.
Linear Allocation As the name suggests, memory is allocated linearly. Throughout this series, I will be using the concept of an allocator as a means to allocate this memory. A linear allocator, is also known by other names such as an Arena or Region-based allocator.
To solve this problem I created a to_raw_pointer
function which happens to work on any "fancy pointer" which implements operator->()
. You can find it in the libc++ implementation.
Here it is:
template <class _Tp>
inline _LIBCPP_INLINE_VISIBILITY
_Tp*
__to_raw_pointer(_Tp* __p) _NOEXCEPT
{
return __p;
}
template <class _Pointer>
inline _LIBCPP_INLINE_VISIBILITY
typename pointer_traits<_Pointer>::element_type*
__to_raw_pointer(_Pointer __p) _NOEXCEPT
{
return _VSTD::__to_raw_pointer(__p.operator->());
}
It works by calling the operator->()
in an unconventional way. This operator must either call another operator->()
, or return a real pointer. The overload for real pointers breaks the recursion with an identity function. So this would be used like:
this->_Getal().construct(__to_raw_pointer(this->_Mylast),
_STD forward<value_type>(this->_Myfirst[_Idx]));
construct
is specified to take a real pointer, not a fancy pointer. And there is no implicit conversion specified from fancy pointers to real pointers. The container must use something such as to_raw_pointer
, or addressof
.
The container is also required to call construct
via allocator_traits
, instead of calling it directly on the stored allocator as shown. This is to allow construct
to be "defaulted" by allocator_traits
, as opposed to requiring the allocator to implement construct
.
Currently both operator*()
and operator->()
generally require the fancy pointer to be non-null prior to calling that operator on it. However I am anticipating that this requirement will be relaxed for operator->()
in the future.
Update
I was in a bit of a hurry when I wrote the above. Now that I have the time, I was going to include the complete requirements on the allocator::pointer
types. However on re-reading the question I see that Maxym has already done a good job of that in the question, so I won't repeat them here.
The one thing that is in the std, but is not completely obvious, is the implicit and explicit conversions among the four pointer types: pointer
, const_pointer
, void_pointer
, and const_void_pointer
:
implicit allocator pointer conversions:
+--------------------------------------+
| pointer --> const_pointer |
| | \ | |
| | --------- | |
| \|/ _\| \|/ |
| void_pointer --> const_void_pointer |
+--------------------------------------+
explicit allocator pointer conversions:
+--------------------------------------+
| pointer const_pointer |
| /|\ /|\ |
| | | |
| | | |
| void_pointer const_void_pointer |
+--------------------------------------+
That is, you can implicitly convert from non-const
to const
, and from non-void
to void
, and you can explicitly convert from void
to non-void
. But there is no way for a container to const_cast
(cast away const
-ness) from an allocator::const_pointer
or allocator::const_void_pointer
. Once the container goes const
, it can never get back.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With