Нашел очень неприятную особенность 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.
Здравствуйте, Videoman, Вы писали:
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.
Здравствуйте, so5team, Вы писали:
S>Суть в том, что в std::map, даже пустом, должна быть некая нода дерева поиска, на которую будет ссылаться итератор, возвращаемый методом end(). Стандарт разрешает реализациям std::map размещать эту ноду в динамической памяти (но не обязывает это делать).
Почему для std::map стандарт разрешает реализациям размещать end() в динамической памяти, а для std::vector — нет?
Здравствуйте, B0FEE664, Вы писали:
BFE>Почему для std::map стандарт разрешает реализациям размещать end() в динамической памяти, а для std::vector — нет?
Ну вообще-то у вектора end() указует за последний элемент в блоке памяти, выделенной как раз в динамической памяти.
Его не надо как-то специально размещать.
Либо я вопрос не понял.
_____________________
С уважением,
Stanislav V. Zudin
Здравствуйте, Stanislav V. Zudin, Вы писали:
BFE>>Почему для std::map стандарт разрешает реализациям размещать end() в динамической памяти, а для std::vector — нет? SVZ>Ну вообще-то у вектора end() указует за последний элемент в блоке памяти, выделенной как раз в динамической памяти.
Что мешает std::map поступать так же?
SVZ>Его не надо как-то специально размещать.
А если вектор пустой, то куда указывает std::vector::end?
Здравствуйте, B0FEE664, Вы писали:
SVZ>>Ну вообще-то у вектора end() указует за последний элемент в блоке памяти, выделенной как раз в динамической памяти. BFE>Что мешает std::map поступать так же?
Эээ... спецификация.
Добавление нового узла не должно портить остальные итераторы. На непрерывном блоке памяти такое гарантировать невозможно.
BFE>А если вектор пустой, то куда указывает std::vector::end?
Например туда же, куда и begin(). Из этого можно вывести, что массив пуст. Или на NULL, тоже легко проверяется.
_____________________
С уважением,
Stanislav V. Zudin
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.
Здравствуйте, Stanislav V. Zudin, Вы писали:
SVZ>>>Ну вообще-то у вектора end() указует за последний элемент в блоке памяти, выделенной как раз в динамической памяти. BFE>>Что мешает std::map поступать так же? SVZ>Эээ... спецификация. SVZ>Добавление нового узла не должно портить остальные итераторы. На непрерывном блоке памяти такое гарантировать невозможно.
Ээээ....
Я немного о другом.
Вот такой псевдокод чем не устраивает?:
BFE>>А если вектор пустой, то куда указывает std::vector::end? SVZ>Например туда же, куда и begin(). Из этого можно вывести, что массив пуст. SVZ>Или на NULL, тоже легко проверяется.
А разве нет требования, чтобы end() итераторы различались у разных объектов?
Здравствуйте, Videoman, Вы писали:
V>Нашел очень неприятную особенность STL в текущем стандарте с которой провозился пол дня, поэтому может быть кому-то будет интересно.
Откапали в C++ залежи костылей? В С++ их просто запредельно много, как ни в каком другом ЯП. Просто не удивляйтесь, для С++ это норма.
Здравствуйте, 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
Здравствуйте, Stanislav V. Zudin, Вы писали:
SVZ>А по стандарту должна. Т.е. итератор end должен быть не просто фуфельным, но хранить указатель на последний валидный объект.
Только не на последний, а на следующий за последним. На последний end() — 1 указывает.
Ну и условие должно выполняться begin() < end(), поэтому он не может быть NULL.
Здравствуйте, Nuzhny, Вы писали:
NB>>у мапы? зачем оно нужно?
N>Чтобы писать цикл, в условии которого будет: it < M.end()
Сравнение на больше/меньше появляются только на уровне категории RandomAccessIterator, к которой итераторы мапы не относятся. Итераторы мапы относятся категории BidirectionalIterator, для которой обязательными являются только операции сравнения на равенство/неравенство. И этих операций достаточно, чтобы написать цикл.
--
Не можешь достичь желаемого — пожелай достигнутого.
Здравствуйте, 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?
-- Пользователи не приняли программу. Всех пришлось уничтожить. --