En C ++, est-ce OK de voler des ressources d'une carte dont je n'ai plus besoin par la suite? Plus précisément, supposons que j'ai un std::map
avec des std::string
clés et que je veux en construire un vecteur en volant les ressources des map
clés s en utilisant std::move
. Notez qu'un tel accès en écriture aux clés corrompt la structure de données interne (ordre des clés) du map
mais je ne l'utiliserai pas par la suite.
Question : Puis-je le faire sans aucun problème ou cela entraînera-t-il des bogues inattendus, par exemple dans le destructeur du, map
car j'y ai accédé d'une manière qui std::map
n'était pas destinée?
Voici un exemple de programme:
#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;
}
Il produit la sortie:
key before stealing: aLongString
key after stealing:
key before stealing: anotherLongString
key after stealing:
EDIT: Je voudrais le faire pour tout le contenu d'une grande carte et enregistrer les allocations dynamiques par ce vol de ressources.
la source
const
valeur est toujours UB.std::string
optimisation des chaînes courtes. Cela signifie qu'il existe une logique non triviale sur la copie et le déplacement et pas seulement l'échange de pointeurs et, en outre, la plupart du temps, le déplacement implique la copie - de peur de traiter avec des chaînes assez longues. La différence statistique était de toute façon faible et en général, elle varie sûrement en fonction du type de traitement de chaîne effectué.Réponses:
Vous faites un comportement indéfini, en utilisant
const_cast
pour modifier uneconst
variable. Ne fais pas ça. La raison enconst
est que les cartes sont triées par leurs clés. Ainsi, la modification d'une clé en place rompt l'hypothèse sous-jacente sur laquelle la carte est construite.Vous ne devez jamais utiliser
const_cast
pour supprimerconst
d'une variable et modifier cette variable.Cela étant dit, C ++ 17 a la solution à votre problème:
std::map
laextract
fonction de:Et non
using namespace std;
. :)la source
map
ne me plaindrai pas si je n'appelle pas ses méthodes (ce que je ne fais pas) et peut-être que l'ordre interne n'est pas important dans son destructeur?const
. La mutation d'unconst
objet est une UB instantanée, que quelque chose y accède ou non par la suite.extract
lorsque vous utilisez des itérateurs comme argument a amorti la complexité constante. Certains frais généraux sont inévitables, mais ils ne seront probablement pas suffisamment importants pour être importants. Si vous avez des exigences spéciales qui ne sont pas couvertes par cela, alors vous devrez implémenter la vôtre enmap
satisfaisant à ces exigences. Lesstd
conteneurs sont destinés à une utilisation générale commune et ne sont pas optimisés pour des cas d'utilisation spécifiques.const
? Dans ce cas, pouvez-vous s'il vous plaît indiquer où mon argumentation est fausse.Votre code tente de modifier des
const
objets, il a donc un comportement indéfini, comme le souligne correctement la réponse de druckermanly .Certaines autres réponses ( celles de Phinz et Deuchie ) soutiennent que la clé ne doit pas être stockée en tant
const
qu'objet car la poignée de nœud résultant de l'extraction de nœuds hors de la carte permet le non-const
accès à la clé. Cette inférence peut sembler plausible au premier abord, mais P0083R3 , l'article qui a introduit lesextract
fonctionnalités), a une section dédiée sur ce sujet qui invalide cet argument:(c'est moi qui souligne)
la source
Je ne pense pas que
const_cast
et la modification conduisent à un comportement indéfini dans ce cas, mais veuillez commenter si cette argumentation est correcte.Cette réponse affirme que
Ainsi, la ligne
v.emplace_back(make_pair(std::move(const_cast<string&>(p.first)),p.second));
de la question ne mène pas à UB si et seulement si l'string
objetp.first
n'a pas été créé en tant qu'objet const. Notez maintenant que la référence sur lesextract
étatsDonc, si je
extract
lenode_handle
correspondant àp
,p
continue de vivre à son emplacement de stockage. Mais après extraction, je suis autorisé àmove
éloigner les ressources dep
comme dans le code de la réponse de druckermanly . Cela signifie quep
et donc aussi l'string
objetp.first
n'a pas été créé comme objet const à l'origine.Par conséquent, je pense que la modification des
map
clés de ne conduit pas à UB et de la réponse de Deuchie , il semble que la structure arborescente maintenant corrompue (maintenant plusieurs mêmes clés de chaîne vides) dumap
n'introduise pas de problèmes dans le destructeur. Ainsi, le code de la question devrait fonctionner correctement au moins en C ++ 17 où laextract
méthode existe (et l'instruction sur les pointeurs restant valides).Mise à jour
Je suis maintenant d'avis que cette réponse est erronée. Je ne le supprime pas car il est référencé par d'autres réponses.
la source
std::launder
.EDIT: Cette réponse est fausse. Des commentaires aimables ont souligné les erreurs, mais je ne les supprime pas car elles ont été référencées dans d'autres réponses.
@druckermanly a répondu à votre première question, qui disait que la modification
map
forcée des clés rompt l'ordre sur lequelmap
la structure de données interne est construite (arbre rouge-noir). Mais il est sûr d'utiliser laextract
méthode car elle fait deux choses: déplacer la clé hors de la carte, puis la supprimer, afin qu'elle n'affecte pas du tout l'ordre de la carte.L'autre question que vous avez posée, à savoir si cela causerait des problèmes lors de la déconstruction, n'est pas un problème. Lorsqu'une carte déconstruit, elle appelle le déconstructeur de chacun de ses éléments (mapped_types, etc.), et la
move
méthode garantit qu'il est sûr de déconstruire une classe après qu'elle a été déplacée. Alors ne t'inquiète pas. En bref, c'est l'opérationmove
qui garantit qu'il est sûr de supprimer ou de réaffecter une nouvelle valeur à la classe "déplacée". Plus précisément pourstring
, lamove
méthode peut définir son pointeur char surnullptr
, afin de ne pas supprimer les données réelles qui ont été déplacées lors de l'appel du déconstructeur de la classe d'origine.Un commentaire m'a rappelé le point que je négligeais, en gros il avait raison mais il y a une chose que je ne suis pas totalement d'accord: ce
const_cast
n'est probablement pas un UB.const
est juste une promesse entre le compilateur et nous. les objets notés commeconst
étant toujours un objet, identiques à ceux qui n'en ont pasconst
, en termes de types et de représentations sous forme binaire. Lorsque leconst
est rejeté, il doit se comporter comme s'il s'agissait d'une classe mutable normale. En ce qui concernemove
, si vous voulez l'utiliser, vous devez passer un&
au lieu d'unconst &
, donc comme je vois que ce n'est pas un UB, cela rompt simplement la promesse deconst
et éloigne les données.J'ai également fait deux expériences, en utilisant MSVC 14.24.28314 et Clang 9.0.0 respectivement, et elles ont donné le même résultat.
production:
Nous pouvons voir que la chaîne '2' est vide maintenant, mais nous avons évidemment rompu l'ordre de la carte car la chaîne vide devrait être repositionnée à l'avant. Si nous essayons d'insérer, de trouver ou de supprimer certains nœuds spécifiques de la carte, cela peut provoquer une catastrophe.
Quoi qu'il en soit, nous pouvons convenir que ce n'est jamais une bonne idée de manipuler les données internes des classes en contournant leurs interfaces publiques. Le
find
,insert
, lesremove
fonctions et ainsi de suite dépendent de leur exactitude sur la recevabilité de la structure de données interne, et qui est la raison pour laquelle nous devons rester loin de la pensée de jeter un oeil à l' intérieur.la source
const
valeur) s'est produit plus tôt. Cependant, l'argumentmove
[fonction] garantit qu'il est sûr de déconstruire [un objet] d'une classe après qu'elle a été déplacée" ne tient pas: vous ne pouvez pas vous déplacer en toute sécurité à partir d'unconst
objet / référence, car cela nécessite une modification, ce quiconst
empêche. Vous pouvez essayer de contourner cette limitation en utilisantconst_cast
, mais à ce stade, vous allez au mieux entrer dans un comportement spécifique à l'implémentation en profondeur, sinon UB.move
fonction prend un&
au lieu d'unconst&
, donc si l'on insiste pour qu'il déplace une clé d'une carte, il doit l'utiliserconst_cast
.const_cast
va donc être désagréable. Cela peut fonctionner la plupart du temps, mais cassez votre code de manière subtile.extract
, nous sommes autorisés à nous déplacer du même objet, non? (voir ma réponse)std::launder