Почему `std::pmr::polymorphic_allocator` не распространяется при перемещении контейнера?

Из http://en.cppreference.com/w/cpp/memory/polymorphic_allocator:

polymorphic_allocator не распространяется на назначение копии контейнера, назначение перемещения или обмен. В результате назначение перемещения polymorphic_allocator-использующего контейнера может привести к ошибке, а замена двух polymorphic_allocator-использующих контейнеров, распределители которых не сравнивают одинаковые результаты, приводит к неопределенному поведению.

Зачем мне такое поведение? Это не только вводит необоснованное неопределенное поведение при подкачке, но и, что более важно для моих целей, подразумевает, что std::pmr::vector фактически является типом, не назначаемым перемещением. Я имею в виду, что его можно назначать перемещением, но это почти гарантированно будет неэффективным.

std::vector<int> v = {1, 2, 3};
std::vector<int> w;
w = std::move(v);  // nocopy, nothrow

std::pmr::monotonic_buffer_resource mr(1000);
std::pmr::vector<int> v( {1, 2, 3}, &mr );
std::pmr::vector<int> w;
w = std::move(v);  // yescopy, yesthrow

Я предполагаю, что это примитивная попытка решить вопросы собственности. Во втором приведенном выше примере v содержит ссылку на mr, но v на самом деле не владеет mr. Разрешение неконтролируемым ссылкам, не принадлежащим владельцам, распространяться по всей системе, как правило, приводит к появлению множества незаметных ошибок. Поэтому вместо того, чтобы изобретать распределитель владения, разработчики решили просто не распространять ссылку на mr. В итоге это привело к плохим последствиям, например, при назначении перемещения вектору теперь копируются его данные; но у вас не так много оборванных указателей на ресурсы памяти. (Некоторые да, но не так много.)


P.S. Я уже вижу, что вы можете избежать копирования/удаления, заранее настроив распределитель, например так:

std::pmr::monotonic_buffer_resource mr(1000);
std::pmr::vector<int> v( {1, 2, 3}, &mr );
std::pmr::vector<int> w(v.get_allocator());
w = std::move(v);  // nocopy, nothrow

person Quuxplusone    schedule 13.07.2017    source источник
comment
это почти гарантированно будет неэффективным Стандарт никогда не гарантировал, что любая операция перемещения-назначения контейнера выполняется за постоянное время. В требованиях прямо сказано: назначение перемещения линейно. Перемещение построение — это постоянное время, а не присваивание. Вообще говоря, люди не так часто перемещаются в существующие контейнеры. Таким образом, это не имеет большого значения. Более того, операция перемещения не будет копировать элементы. Это просто переместит их. Так что это не то же самое, что сказать, что нет задания на перемещение.   -  person Nicol Bolas    schedule 13.07.2017


Ответы (1)


allocator_traits<>::propagate_on_container_copy/move_assignment/swap — это статические свойства распределителя. Свойства полиморфного распределителя по своей природе определяются во время времени выполнения. Некоторые полиморфные распределители могут распространяться на другие, а некоторые — нет.

Следовательно, невозможно, чтобы эти свойства были известны во время компиляции. Таким образом, распределитель PMR должен предполагать наихудший случай во время компиляции: отсутствие распространения.

Возьмем ваш пример с изменением:

std::pmr::monotonic_buffer_resource mr(1000);
std::pmr::vector<int> v( {1, 2, 3}, &mr );
std::pmr::vector<int> w;
auto a1 = w.get_allocator();
w = std::move(v);
assert(w.get_allocator() == a1);

Стандарт C++ требует, чтобы это не утверждалось. Присваивание не перемещает распределитель контейнера; вот как назначение контейнера работает с распределителями в C++.

Следовательно, либо распределитель целевого контейнера может обрабатывать память исходного контейнера, либо нет. А если не может, то целевой контейнер должен выделить память и скопировать/переместить объекты из источника.

Является ли оператор присваивания перемещения noexcept или нет, является статическим свойством оператора. А так как PMR не могут знать до момента выполнения, могут ли они распространять память, контейнер должен обозначить свой оператор присваивания перемещения как присваивание переноса.

Во время выполнения он может фактически решить, возможно ли распространение или нет. И если это возможно, то он пойдет по более эффективному пути.

person Nicol Bolas    schedule 13.07.2017
comment
@Nicol_Bolas Меня немного смущает предложение Некоторые полиморфные распределители могут распространяться на другие, а некоторые нет. Я имею в виду, что polymorphic_allocator просто держит указатель на memory_resource, чтобы использовать его для распределения. Мое единственное предположение состоит в том, что это относится к случаю, когда ресурс памяти, используемый полиморфным_аллокатором, предназначен только для использования для выделения определенного экземпляра, скажем, std::pmr::vector? Это правильно? - person rmawatson; 07.12.2018