В C ++ нормально ли красть ресурсы с карты, которая мне больше не нужна? Точнее, предположим, что у меня есть ключи std::map
с std::string
ключами, и я хочу создать из него вектор путем кражи ресурсов map
с использованием ключей s std::move
. Обратите внимание, что такой доступ для записи ключей нарушает внутреннюю структуру данных (порядок ключей), map
но впоследствии я не буду использовать его.
Вопрос : Могу ли я сделать это без каких-либо проблем или это приведет к неожиданным ошибкам, например, в деструкторе, map
потому что я получил к нему доступ таким образом, для std::map
которого он не предназначен?
Вот пример программы:
#include<map>
#include<string>
#include<vector>
#include<iostream>
using namespace std;
int main(int argc, char *argv[])
{
std::vector<std::pair<std::string,double>> v;
{ // new scope to make clear that m is not needed
// after the resources were stolen
std::map<std::string,double> m;
m["aLongString"]=1.0;
m["anotherLongString"]=2.0;
//
// now steal resources
for (auto &p : m) {
// according to my IDE, p has type
// std::pair<const class std::__cxx11::basic_string<char>, double>&
cout<<"key before stealing: "<<p.first<<endl;
v.emplace_back(make_pair(std::move(const_cast<string&>(p.first)),p.second));
cout<<"key after stealing: "<<p.first<<endl;
}
}
// now use v
return 0;
}
Он производит вывод:
key before stealing: aLongString
key after stealing:
key before stealing: anotherLongString
key after stealing:
РЕДАКТИРОВАТЬ: Я хотел бы сделать это для всего содержимого большой карты и сохранить динамическое распределение путем кражи этого ресурса.
источник
const
значения всегда UB.std::string
есть оптимизация коротких строк. Это означает, что существует некоторая нетривиальная логика при копировании и перемещении, а не просто обмен указателями, и, кроме того, в большинстве случаев перемещение подразумевает копирование, чтобы вы не имели дело с довольно длинными строками. В любом случае статистическая разница была небольшой, и в целом она, несомненно, варьируется в зависимости от того, какой тип обработки строки выполняется.Ответы:
Вы делаете неопределенное поведение, используя
const_cast
для измененияconst
переменной. Не делай этого. Причина вconst
том, что карты отсортированы по ключам. Таким образом, изменение ключа на месте нарушает основополагающее предположение, на котором построена карта.Вы никогда не должны использовать
const_cast
для удаленияconst
из переменной и изменения этой переменной.Тем не менее, C ++ 17 имеет решение для вашей проблемы:
std::map
'sextract
function:И не надо
using namespace std;
. :)источник
map
не будет жаловаться, если я не буду вызывать его методы (что я не делаю), и, возможно, внутренний порядок не важен в его деструкторе?const
. Мутированиеconst
объекта происходит мгновенно, независимо от того, получает ли он что-либо на самом деле впоследствии.extract
при использовании итераторов в качестве аргумента, амортизируется постоянная сложность. Некоторые накладные расходы неизбежны, но, вероятно, они не будут достаточно значительными, чтобы иметь значение. Если у вас есть особые требования, не охватываемые этим, то вам нужно будет реализовать свои собственныеmap
требования, удовлетворяющие этим требованиям. Этиstd
контейнеры предназначены для общего назначения application.They общего не оптимизированы для конкретных случаев применения.const
? В этом случае не могли бы вы указать, где мои аргументы неверны.Ваш код пытается изменить
const
объекты, поэтому он имеет неопределенное поведение, как правильно указывает ответ druckermanly .В некоторых других ответах ( Финса и Дьючи ) утверждается, что ключ не должен храниться как
const
объект, поскольку дескриптор узла, полученный в результате извлечения узлов из карты, допускает отсутствиеconst
доступа к ключу. На первый взгляд этот вывод может показаться правдоподобным, но P0083R3 , статья, которая представилаextract
функциональные возможности), имеет специальный раздел на эту тему, который лишает законной силы этот аргумент:(акцент мой)
источник
Я не думаю, что
const_cast
модификация приводит к неопределенному поведению в этом случае, но, пожалуйста, прокомментируйте, верна ли эта аргументация.Этот ответ утверждает, что
Таким образом, строка
v.emplace_back(make_pair(std::move(const_cast<string&>(p.first)),p.second));
в вопросе не ведет к UB тогда и только тогда, когдаstring
объектp.first
не был создан как объект const. Теперь обратите внимание, что ссылка наextract
штатыТак что, если я , соответствующий , продолжает жить в своем месте хранения. Но после извлечения мне позволено убрать ресурсы, как в коде ответа Дракерманели . Это означает, что и, следовательно, также объект не был изначально создан как объект const.
extract
node_handle
p
p
move
p
p
string
p.first
Поэтому, я думаю, что модификация
map
ключей не приводит к UB, и из ответа Деучи , кажется, что также теперь поврежденная древовидная структура (теперь несколько одинаковых пустых строковых ключей)map
не создает проблем в деструкторе. Таким образом, код в вопросе должен нормально работать, по крайней мере, в C ++ 17, гдеextract
метод существует (и утверждение о том, что указатели остаются действительными, сохраняется).Обновить
Теперь я считаю, что этот ответ неверен. Я не удаляю его, потому что на него ссылаются другие ответы.
источник
std::launder
.РЕДАКТИРОВАТЬ: Этот ответ неверен. Добрые комментарии указали на ошибки, но я не удаляю их, потому что на них есть ссылки в других ответах.
@druckermanly ответил на ваш первый вопрос, в котором говорилось, что
map
принудительное изменение ключей нарушает порядок, на которомmap
строится внутренняя структура данных (красно-черное дерево). Но использоватьextract
метод безопасно, потому что он делает две вещи: убирает ключ с карты и затем удаляет его, чтобы он никак не влиял на упорядоченность карты.Другой вопрос, который вы задали относительно того, не вызовет ли это проблемы при деконструкции, не является проблемой. Когда карта деконструируется, она вызывает деконструктор каждого из своих элементов (mapped_types и т. Д.), И
move
метод гарантирует, что безопасно деконструировать класс после его перемещения. Так что не волнуйся. В двух словах, именно эта операцияmove
гарантирует, что можно безопасно удалить или переназначить какое-то новое значение «перемещенному» классу. Специально дляstring
,move
метод может установить свой указатель на символnullptr
, поэтому он не удаляет фактические данные, которые были перемещены, когда был вызван деконструктор исходного класса.Комментарий напомнил мне пункт, который я пропустил, в основном он был прав, но есть одна вещь, с которой я не полностью согласен:
const_cast
это, вероятно, не UB.const
это просто обещание между компилятором и нами. объекты, помеченные какconst
все еще являются объектами, такими же, как те, с которыми они не имеютconst
, с точки зрения их типов и представлений в двоичной форме. Когдаconst
объект отбрасывается, он должен вести себя так, как будто это обычный изменяемый класс. Что касаетсяmove
, если вы хотите использовать его, вы должны передать&
вместо aconst &
, так как я вижу, что это не UB, это просто нарушает обещаниеconst
и удаляет данные.Я также провел два эксперимента, используя MSVC 14.24.28314 и Clang 9.0.0 соответственно, и они дали тот же результат.
вывод:
Мы можем видеть, что строка '2' теперь пуста, но, очевидно, мы нарушили упорядоченность карты, потому что пустая строка должна быть перемещена вперед. Если мы попытаемся вставить, найти или удалить некоторые конкретные узлы карты, это может привести к катастрофе.
В любом случае, мы можем согласиться с тем, что никогда не следует манипулировать внутренними данными любых классов, минуя их общедоступные интерфейсы.
find
,insert
,remove
Функции и так далее полагаться на правильность их упорядоченности внутренней структуры данных, и что является причиной , почему мы должны держаться подальше от мысли о том, заглядывая внутрь.источник
const
значения) произошло раньше. Однако аргумент «move
[функция] гарантирует, что безопасно деконструировать [объект] класса после того, как он был перемещен» не выполняется: вы не можете безопасно перемещаться изconst
объекта / ссылки, так как это требует модификации, котораяconst
предотвращает. Вы можете попытаться обойти это ограничение с помощьюconst_cast
, но в этот момент вы в лучшем случае углубляетесь в поведение, специфичное для реализации, если не UB.move
функция принимает&
вместо aconst&
, поэтому, если кто-то настаивает на том, что он перемещает ключ с карты, он должен использоватьconst_cast
.const_cast
будет неприятным. Это может работать большую часть времени, но неуклюже нарушает ваш код.extract
этого нам разрешается двигаться от одного и того же объекта, верно? (см. мой ответ)std::launder