简体   繁体   中英

Memory allocator with custom pointer type

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 , and X::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 and X::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 (eg 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);
};

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM