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

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

... вектор (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]));

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

[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, они добавят еще несколько требований:

[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 и необработанный указатель равны. Мне интересно, что я пропустил?

РЕДАКТИРОВАТЬ: я добавлю здесь часть определения my_allocator:

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

person MaksymB    schedule 25.12.2014    source источник
comment
Можете ли вы предоставить нам все определения типов в вашем распределителе? Например. const_pointer и так далее? (Меня совершенно не удивит, если стандартная библиотека VC++ не будет полностью совместима)   -  person Columbo    schedule 25.12.2014
comment
Скорее всего, стандартная библиотека VC++ содержит ошибки. Я думал, что создание std содержащихся объектов должно выполняться через allocator_traits::construct, а не напрямую allocator::construct.   -  person Walter    schedule 25.12.2014
comment
@Columbo, я добавил код, который ты просил.   -  person MaksymB    schedule 25.12.2014
comment
@Walter, на самом деле он использует allocator_traits. _Getal() возвращает обёртку std::_Wrap_alloc‹ my_allocator‹ int › ›.   -  person MaksymB    schedule 25.12.2014
comment
Здравствуйте, Максим, вам наконец удалось запустить свой аллокатор с пользовательским указателем на VS STL? Я пытаюсь создать аналогичный код на VS2015 и застрял на той же проблеме. Я вижу, что у boost/interprocess есть специальный набор контейнеров для решения такой проблемы (например, он сохраняет данные в файле отображения памяти), но я бы хотел придерживаться минимального набора сторонних библиотек, насколько это возможно...   -  person Aleksander Stankiewicz    schedule 25.08.2016
comment
Нет, я прибег к обходному пути. Это явно ошибка, и, насколько я знаю, она до сих пор не исправлена ​​в VS2015. Я не знал об этих обходных путях в boost. Спасибо, что указали!   -  person MaksymB    schedule 25.08.2016


Ответы (1)


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

Вот:

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 в не-void. Но у контейнера нет возможности const_cast (отбросить const-ность) из allocator::const_pointer или allocator::const_void_pointer. Как только контейнер уходит const, он никогда не сможет вернуться.

person Howard Hinnant    schedule 25.12.2014
comment
Круто, но на самом деле это то же самое, что и addressof(*(this-›_Mylast)) потому что косвенность указателя в любом случае требуется стандартом. Я пытаюсь выяснить, делаю ли я что-то неправильно (потому что неправильно истолковываю стандарт С++) или это ошибка в реализации VS STL. - person MaksymB; 25.12.2014
comment
Итак, правильно ли я делаю вывод, что это ошибка в реализации VC++ STL? (Поскольку X::pointer на необработанный указатель и наоборот не допускается). - person MaksymB; 26.12.2014
comment
Да: construct указан для получения реального указателя, а не причудливого указателя. И нет неявного преобразования, указанного из причудливых указателей в настоящие указатели. Контейнер должен использовать что-то вроде to_raw_pointer или addressof. - person Howard Hinnant; 26.12.2014
comment
Это предпочтительнее addressof( * ptr )? Ни operator*, ни operator-> не требуются, но * интуитивно кажется более безопасным. - person Potatoswatter; 27.05.2015
comment
Тонкостью является вопрос, можно ли разыменовать ptr или нет. Если вы не можете, то, очевидно, addressof( * ptr ) не сработает. В настоящее время стандарт говорит, что для использования operator-> с ptr также требуется возможность разыменования ptr. Я считаю, что стандарт слишком строг в этом требовании. Только если вы выберете элемент с operator->, вам нужно разыменовать его. Если вы вызываете ptr.operator->(), ptr на самом деле не разыменовывается. Стандарт должен быть разъяснен, чтобы сказать так. - person Howard Hinnant; 27.05.2015
comment
OK. Таким образом, operator-> работает с потенциально нулевыми значениями, а addressof работает с более небрежными реализациями указателей (и теми, которые не предназначены для поддержки типов классов). allocator_traits::allocate не может возвращать nullptr, поэтому текущий случай может относиться ко второй категории. - person Potatoswatter; 28.05.2015
comment
Два момента: 1. Если вы попросите allocate выделить нулевые байты, результат не будет указан (может быть не разыменован). 2. vector::data() — это первый кандидат, которому обычно требуется преобразовать нулевой allocator::pointer во встроенный указатель с максимальной эффективностью. При всем при этом для использования с construct я не могу себе представить случая, когда решение addressof не сработало бы нормально. - person Howard Hinnant; 28.05.2015