Распределитель памяти с пользовательским типом указателя

Я попытался создать специализированный распределитель памяти, который использует интеллектуальный указатель. Я не размещаю код, потому что он слишком большой и не содержит большой информации. Затем я протестировал его с помощью std::vector. Он отлично работает на Xcode. Но когда я попытался создать тот же код в Visual Studio 12 (2013), сбой сборки произошел со следующей ошибкой:

... vector (873): ошибка C2660: 'std::_Wrap_alloc< my_allocator< int > >::construct': функция не принимает 2 аргумента

проблема заключается в методе push_back:

void push_back(value_type&& _Val)
    {
    ....
        this->_Getal().construct(this->_Mylast,
            _STD forward<value_type>(this->_Myfirst[_Idx]));
    ....
    }

Сообщение об ошибке немного запутанно. Реальная проблема заключается в том, что this->_Mylast имеет тип my_allocator< int >::pointer, который является умным указателем, а метод построения ожидает int*.

Итак, вопрос прост: каковы требования к типам указателей, которые используются в специализированном распределителе памяти? Должен ли X::pointer быть конвертируемым в необработанный указатель? Если да, это делает их довольно бесполезными.

На самом деле я ожидал бы, что строка кода выглядит следующим образом:

this->_Getal().construct(addressof(*(this->_Mylast)),
            _STD forward<value_type>(this->_Myfirst[_Idx]));

Попробуйте найти ответ в стандарте С++, в котором говорится:

[17.6.3.5-5] Тип распределителя X должен удовлетворять требованиям CopyConstructible (17.6.3.1). Типы X::pointer, X::const_pointer, X::void_pointer и X::const_void_pointer должны удовлетворять требованиям NullablePointer (17.6.3.3). Никакой конструктор, оператор сравнения, операция копирования, операция перемещения или операция свопинга на этих типах не должны выходить из-за исключения. X::pointer и X::const_pointer также должны удовлетворять требованиям для итератора произвольного доступа (24.2)

Если мы посмотрим на NullablePointer reqs, они добавят несколько других требований:

[17.6.3.3] Тип NullablePointer - это указательный тип, поддерживающий нулевые значения. Тип P соответствует требованиям NullablePointer, если:
(1.1) - P удовлетворяет требованиям EqualityComparable, DefaultConstructible, CopyConstructible, CopyAssignable и Destructible...

Если я проверяю требования к итератору произвольного доступа, я также не нахожу никакого явного упоминания о его литье к необработанному указателю. Но в нескольких местах используется подход с addressof (например, 24.2.1-5).

Кроме того, это не единственное место в реализации Microsoft std::vector, где X::pointer и необработанный указатель считаются равными. Мне интересно, что мне не хватает?

EDIT: я добавлю часть my_allocator deffinition здесь:

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);
};

Ответ 1

Чтобы решить эту проблему, я создал функцию to_raw_pointer, которая работает для любого "причудливого указателя", который реализует operator->(). Вы можете найти его в libС++.

Вот он:

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->());
}

Он работает, вызывая operator->() нетрадиционным способом. Этот оператор должен либо вызвать другой operator->(), либо вернуть реальный указатель. Перегрузка для реальных указателей ломает рекурсию с помощью функции идентификации. Таким образом, это будет использоваться как:

this->_Getal().construct(__to_raw_pointer(this->_Mylast),
            _STD forward<value_type>(this->_Myfirst[_Idx]));

construct указан, чтобы взять реальный указатель, а не причудливый указатель. И нет никакого неявного преобразования, указанного от причудливых указателей до реальных указателей. Контейнер должен использовать что-то вроде to_raw_pointer или addressof.

Контейнер также должен вызывать construct через allocator_traits вместо того, чтобы называть его непосредственно на сохраненном распределителе, как показано. Это означает, что construct "по умолчанию" используется allocator_traits, а не требует, чтобы распределитель реализовал construct.

В настоящее время как operator*(), так и operator->() обычно требуется, чтобы причудливый указатель был непустым до вызова этого оператора на нем. Однако я ожидаю, что это требование будет смягчено для operator->() в будущем.

Обновление

Я очень торопился, когда писал выше. Теперь, когда у меня есть время, я собираюсь включить полные требования к типам allocator::pointer. Однако при повторном чтении вопроса я вижу, что Maxym уже хорошо справился с этим в вопросе, поэтому я не буду повторять их здесь.

Единственное, что находится в std, но не полностью очевидно, - это неявные и явные преобразования между четырьмя типами указателей: pointer, const_pointer, void_pointer и 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 |
+--------------------------------------+

То есть вы можете неявно конвертировать из не const в const, а от не void в void, и вы можете явно преобразовать из void в non void. Но нет контейнера для const_cast (отбрасывание const -ness) из allocator::const_pointer или allocator::const_void_pointer. Как только контейнер отправится const, он никогда не сможет вернуться.