Move конструкторы STL map и unorderedmap
От: Videoman Россия https://hts.tv/
Дата: 21.11.19 13:49
Оценка: 8 (1)
Нашел очень неприятную особенность STL в текущем стандарте с которой провозился пол дня, поэтому может быть кому-то будет интересно.

Проблема:
Есть обыкновенный класс, назовем его "агрегатор" — класс, в нем несколько членов данных других классов, в которых, в свою очередь, другие члены и т.д. Код классов был под VS 2013 студию, где, в силу специфики реализации, все конструкторы перемещения были написаны явно с перемещение членов вручную и не было noexcept спецификатора. Переползли на VS 2017 с С++17. Появилась возможность писать = default для конструкторов перемещения и выкинуть избыточный код.

Теперь внимание:
Для самого корневого класса, меняю реализацию конструктора перемещения на = default и все перестает компилироваться со странными ошибками что класс не может быть скопирован (не перемещен, а именно скопирован). Уж сколько я боролся пытаясь выяснить что не так, пока не понял что дело в noexcept и не начал последовательно выкидывать члены классов, пока не дошел до std::map и std::unorderedmap и не выяснил что по стандарту у них конструкторы не noexcept .

Теперь вопрос, что это — диверсия?

Получается что если я захочу использовать в качестве члена класса std::map или std::unorderedmap, то я обязан пометить конструктор перемещения класса как "не noexcept", а заодно и во всей, возможно очень длинной, как у меня, цепочке классов которые используют этот класс в качестве члена?

std::vector, содержащий внутри std::map, всегда использует копирование при расширении?

Получается, что с одной стороны STL контейнеры требуют от меня указывать noexcept, иначе внутри них будет происходить копирование вместо перемещения, а с другой стороны, я не могу это сделать, так как внутри, внутри, внутри какого-то класса вдруг может понадобится std::map или std::unorderedmap.

Кто что думает по этому поводу?
Re: Move конструкторы STL map и unorderedmap
От: Wawan Россия http://www.wawan.ru/resume
Дата: 21.11.19 14:03
Оценка: :)
Здравствуйте, Videoman, Вы писали:
V>Кто что думает по этому поводу?

за полный контроль самописных контейнеров
Re: Move конструкторы STL map и unorderedmap
От: B0FEE664  
Дата: 21.11.19 14:37
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Кто что думает по этому поводу?


Я сторонник использования умных указателей, поэтому меня все эти проблемы перемещения и копирования контейнеров не заботят, но вопрос действительно интересный, почему в стандарте:

vector(vector&&) noexcept;

но
map(map&& x);



а в реализации gcc
      map(map&&) = default;

И каждый день — без права на ошибку...
Re: Move конструкторы STL map и unorderedmap
От: so5team https://stiffstream.com
Дата: 21.11.19 14:43
Оценка: 16 (2) +1
Здравствуйте, Videoman, Вы писали:

V>Теперь вопрос, что это — диверсия?


Есть объяснение этому на StackOverflow: https://stackoverflow.com/questions/57299324/why-is-stdmaps-move-constructor-not-noexcept
Re[2]: Move конструкторы STL map и unorderedmap
От: Videoman Россия https://hts.tv/
Дата: 21.11.19 14:49
Оценка: +1
Здравствуйте, so5team, Вы писали:

S>Есть объяснение этому на StackOverflow: https://stackoverflow.com/questions/57299324/why-is-stdmaps-move-constructor-not-noexcept


Я конечно видел это объяснение, но не понял ничего. Если вас не затруднит, вы не могли бы объяснить своими словами в чем проблема?
Re[3]: Move конструкторы STL map и unorderedmap
От: so5team https://stiffstream.com
Дата: 21.11.19 15:02
Оценка:
Здравствуйте, Videoman, Вы писали:

S>>Есть объяснение этому на StackOverflow: https://stackoverflow.com/questions/57299324/why-is-stdmaps-move-constructor-not-noexcept


V>Я конечно видел это объяснение, но не понял ничего. Если вас не затруднит, вы не могли бы объяснить своими словами в чем проблема?


Суть в том, что в std::map, даже пустом, должна быть некая нода дерева поиска, на которую будет ссылаться итератор, возвращаемый методом end(). Стандарт разрешает реализациям std::map размещать эту ноду в динамической памяти (но не обязывает это делать). Поэтому в каких-то реализациях STL, в частности от MS, эта нода в std::map как раз в динамической памяти и размещается.

Когда происходит перемещение содержимого из одного std::map (пусть это будет A) в другой (пусть это будет B), то std::map A должен остаться в корректном состоянии. Т.е. метод end() для A должен возвращать итератор, ссылающийся на какую-то ноду. Но что это за нода, ведь все содержимое A перехало в B?

Эту новую ноду после переезда нужно создать для A заново. И как раз при создании может быть брошено исключение. Поэтому move constructor для такого std::map не может быть noexcept.
Re[4]: Move конструкторы STL map и unorderedmap
От: B0FEE664  
Дата: 21.11.19 15:13
Оценка: :)
Здравствуйте, so5team, Вы писали:

S>Суть в том, что в std::map, даже пустом, должна быть некая нода дерева поиска, на которую будет ссылаться итератор, возвращаемый методом end(). Стандарт разрешает реализациям std::map размещать эту ноду в динамической памяти (но не обязывает это делать).


Почему для std::map стандарт разрешает реализациям размещать end() в динамической памяти, а для std::vector — нет?
И каждый день — без права на ошибку...
Re[5]: Move конструкторы STL map и unorderedmap
От: Stanislav V. Zudin Россия  
Дата: 21.11.19 15:27
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>Почему для std::map стандарт разрешает реализациям размещать end() в динамической памяти, а для std::vector — нет?


Ну вообще-то у вектора end() указует за последний элемент в блоке памяти, выделенной как раз в динамической памяти.
Его не надо как-то специально размещать.

Либо я вопрос не понял.
_____________________
С уважением,
Stanislav V. Zudin
Re[6]: Move конструкторы STL map и unorderedmap
От: B0FEE664  
Дата: 21.11.19 15:36
Оценка:
Здравствуйте, Stanislav V. Zudin, Вы писали:

BFE>>Почему для std::map стандарт разрешает реализациям размещать end() в динамической памяти, а для std::vector — нет?

SVZ>Ну вообще-то у вектора end() указует за последний элемент в блоке памяти, выделенной как раз в динамической памяти.
Что мешает std::map поступать так же?

SVZ>Его не надо как-то специально размещать.


А если вектор пустой, то куда указывает std::vector::end?
И каждый день — без права на ошибку...
Re[7]: Move конструкторы STL map и unorderedmap
От: Stanislav V. Zudin Россия  
Дата: 21.11.19 15:40
Оценка:
Здравствуйте, B0FEE664, Вы писали:

SVZ>>Ну вообще-то у вектора end() указует за последний элемент в блоке памяти, выделенной как раз в динамической памяти.

BFE>Что мешает std::map поступать так же?

Эээ... спецификация.
Добавление нового узла не должно портить остальные итераторы. На непрерывном блоке памяти такое гарантировать невозможно.


BFE>А если вектор пустой, то куда указывает std::vector::end?


Например туда же, куда и begin(). Из этого можно вывести, что массив пуст. Или на NULL, тоже легко проверяется.
_____________________
С уважением,
Stanislav V. Zudin
Re[2]: Move конструкторы STL map и unorderedmap
От: Шахтер Интернет  
Дата: 21.11.19 15:54
Оценка:
Здравствуйте, so5team, Вы писали:

S>Здравствуйте, Videoman, Вы писали:


V>>Теперь вопрос, что это — диверсия?


S>Есть объяснение этому на StackOverflow: https://stackoverflow.com/questions/57299324/why-is-stdmaps-move-constructor-not-noexcept


It is because I couldn't talk all of the implementors into a resource-less state that the map could be put into.


В переводе на русский: "так сделано в угоду дебилам, реализующим STL".
Слава Богу, меня это всё не касается.
Я достаточно долго живу, чтобы понять, что Майкрософт не способна написать С++ компилятор, поэтому я не пользуюсь VC.
Я достаточно долго живу, чтобы понять, что разработчики стандарта и стандартной библиотеки не способны сделать хороший продукт.
Поэтому я сделал CCore.

А вам всем -- счастливого траха.
В XXI век с CCore.
Копай Нео, копай -- летать научишься. © Matrix. Парадоксы
Re[8]: Move конструкторы STL map и unorderedmap
От: B0FEE664  
Дата: 21.11.19 16:59
Оценка:
Здравствуйте, Stanislav V. Zudin, Вы писали:

SVZ>>>Ну вообще-то у вектора end() указует за последний элемент в блоке памяти, выделенной как раз в динамической памяти.

BFE>>Что мешает std::map поступать так же?
SVZ>Эээ... спецификация.
SVZ>Добавление нового узла не должно портить остальные итераторы. На непрерывном блоке памяти такое гарантировать невозможно.
Ээээ....
Я немного о другом.
Вот такой псевдокод чем не устраивает?:
class map
{
  tree_node* root;

  map()
  {
    root = nullptr;
  }

  iterator begin() 
  {
    return nullptr == root ? root + 1 : root;
  }

  iterator end() 
  {
    return root + 1;
  }
};


BFE>>А если вектор пустой, то куда указывает std::vector::end?

SVZ>Например туда же, куда и begin(). Из этого можно вывести, что массив пуст.
SVZ>Или на NULL, тоже легко проверяется.

А разве нет требования, чтобы end() итераторы различались у разных объектов?
И каждый день — без права на ошибку...
Re: Move конструкторы STL map и unorderedmap
От: smeeld  
Дата: 21.11.19 17:21
Оценка: -3 :)
Здравствуйте, Videoman, Вы писали:

V>Нашел очень неприятную особенность STL в текущем стандарте с которой провозился пол дня, поэтому может быть кому-то будет интересно.


Откапали в C++ залежи костылей? В С++ их просто запредельно много, как ни в каком другом ЯП. Просто не удивляйтесь, для С++ это норма.
Re[9]: Move конструкторы STL map и unorderedmap
От: Stanislav V. Zudin Россия  
Дата: 21.11.19 18:26
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>>>Что мешает std::map поступать так же?

SVZ>>Эээ... спецификация.
SVZ>>Добавление нового узла не должно портить остальные итераторы. На непрерывном блоке памяти такое гарантировать невозможно.
BFE>Ээээ....
BFE>Я немного о другом.
BFE>Вот такой псевдокод чем не устраивает?:

В принципе значения из специального диапазона, в котором точно не окажется валидных объектов, вполне имеет право на существование.
Правда я не готов сказать, применим ли такой подход на всех платформах.
Может где-то нет такой "запрещенной зоны", например, в ДОСе.

Но вот вопрос: а сможет ли твоя реализация обеспечить корректность такого кода:
iterator last_hero = m_map.end() - 1;


А по стандарту должна. Т.е. итератор end должен быть не просто фуфельным, но хранить указатель на последний валидный объект.

BFE>>>А если вектор пустой, то куда указывает std::vector::end?

SVZ>>Например туда же, куда и begin(). Из этого можно вывести, что массив пуст.
SVZ>>Или на NULL, тоже легко проверяется.

BFE>А разве нет требования, чтобы end() итераторы различались у разных объектов?


Ну у непустых контейнеров они и будут различаться. Так сказать, естественным образом.
_____________________
С уважением,
Stanislav V. Zudin
Re[10]: Move конструкторы STL map и unorderedmap
От: Nuzhny Россия https://github.com/Nuzhny007
Дата: 21.11.19 18:37
Оценка:
Здравствуйте, Stanislav V. Zudin, Вы писали:

SVZ>А по стандарту должна. Т.е. итератор end должен быть не просто фуфельным, но хранить указатель на последний валидный объект.


Только не на последний, а на следующий за последним. На последний end() — 1 указывает.
Ну и условие должно выполняться begin() < end(), поэтому он не может быть NULL.
Re[11]: Move конструкторы STL map и unorderedmap
От: night beast СССР  
Дата: 21.11.19 18:45
Оценка: +1
Здравствуйте, Nuzhny, Вы писали:

N>Ну и условие должно выполняться begin() < end(), поэтому он не может быть NULL.


у мапы? зачем оно нужно?
Re[12]: Move конструкторы STL map и unorderedmap
От: Nuzhny Россия https://github.com/Nuzhny007
Дата: 21.11.19 19:26
Оценка:
Здравствуйте, night beast, Вы писали:

NB>у мапы? зачем оно нужно?


Чтобы писать цикл, в условии которого будет: it < M.end()
Re[13]: Move конструкторы STL map и unorderedmap
От: night beast СССР  
Дата: 21.11.19 19:34
Оценка: +2
Здравствуйте, Nuzhny, Вы писали:

NB>>у мапы? зачем оно нужно?


N>Чтобы писать цикл, в условии которого будет: it < M.end()


и зачем такое условие нужно? для итераторов такие сравнения в принципе редко встречаются...
как ты в оцениваешь сложность этой операции для мапы?
Re[13]: Move конструкторы STL map и unorderedmap
От: rg45 СССР  
Дата: 21.11.19 19:55
Оценка: 12 (1) +4
Здравствуйте, Nuzhny, Вы писали:

NB>>у мапы? зачем оно нужно?


N>Чтобы писать цикл, в условии которого будет: it < M.end()


Сравнение на больше/меньше появляются только на уровне категории RandomAccessIterator, к которой итераторы мапы не относятся. Итераторы мапы относятся категории BidirectionalIterator, для которой обязательными являются только операции сравнения на равенство/неравенство. И этих операций достаточно, чтобы написать цикл.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[4]: Move конструкторы STL map и unorderedmap
От: Коваленко Дмитрий Россия http://www.ibprovider.com
Дата: 22.11.19 07:06
Оценка:
Здравствуйте, so5team, Вы писали:

S>>>Есть объяснение этому на StackOverflow: https://stackoverflow.com/questions/57299324/why-is-stdmaps-move-constructor-not-noexcept


V>>Я конечно видел это объяснение, но не понял ничего. Если вас не затруднит, вы не могли бы объяснить своими словами в чем проблема?


S>Суть в том, что в std::map, даже пустом, должна быть некая нода дерева поиска, на которую будет ссылаться итератор, возвращаемый методом end().


А зачем она там нужна, эта dummy-нода?

Чтобы end() одного пустого map отличался от end() другого пустого map?
-- Пользователи не приняли программу. Всех пришлось уничтожить. --
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.