En C ++, ¿está bien robar recursos de un mapa que ya no necesito? Más precisamente, suponga que tengo un std::mapcon std::stringclaves y quiero construir un vector a partir de él robando los recursos de las mapteclas s usando std::move. Tenga en cuenta que dicho acceso de escritura a las claves corrompe la estructura de datos interna (orden de las claves) del mappero no la usaré después.
Pregunta : ¿Puedo hacer esto sin ningún problema o esto conducirá a errores inesperados, por ejemplo, en el destructor del mapporque lo accedí de una manera que std::mapno estaba destinada?
Aquí hay un programa de ejemplo:
#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;
}
Produce la salida:
key before stealing: aLongString
key after stealing:
key before stealing: anotherLongString
key after stealing:
EDITAR: Me gustaría hacer esto para todo el contenido de un mapa grande y guardar asignaciones dinámicas mediante este robo de recursos.
fuente

constvalor siempre es UB.std::stringtiene una optimización de cadena corta. Lo que significa que existe una lógica no trivial en la copia y el movimiento y no solo el intercambio de punteros y, además, la mayoría de las veces el movimiento implica la copia, para que no trabaje con cadenas bastante largas. La diferencia estadística fue pequeña de todos modos y, en general, seguramente varía según el tipo de procesamiento de cadena que se realice.Respuestas:
Estás haciendo un comportamiento indefinido, usando
const_castpara modificar unaconstvariable. No hagas eso. La razónconstes porque los mapas están ordenados por sus claves. Por lo tanto, modificar una clave en el lugar está rompiendo la suposición subyacente en la que se basa el mapa.Nunca debe usar
const_castpara eliminarconstde una variable y modificar esa variable.Dicho esto, C ++ 17 tiene la solución para su problema:
std::maplaextractfunción de:Y no lo hagas
using namespace std;. :)fuente
mapque no se quejarán si no llamo a sus métodos (que no hago) y tal vez el orden interno no es importante en su destructor.const. La mutación de unconstobjeto es UB instantáneo, ya sea que algo realmente acceda a él o no después.extractcuando se usan iteradores como argumento, se ha amortizado la complejidad constante. Algunos gastos generales son inevitables, pero probablemente no serán lo suficientemente importantes como para importar. Si tiene requisitos especiales que no están cubiertos por esto, deberá implementar sus propiosmaprequisitos. Losstdcontenedores están destinados a una aplicación común de uso general. No están optimizados para casos de uso específicos.const? En este caso, ¿puede indicar dónde está mal mi argumentación ?Su código intenta modificar
constobjetos, por lo que tiene un comportamiento indefinido, como señala correctamente la respuesta de druckermanly .Algunas otras respuestas (de Phinz y Deuchie ) argumentan que la clave no debe almacenarse como un
constobjeto porque el identificador de nodo resultante de la extracción de nodos fuera del mapa permite el noconstacceso a la clave. Esta inferencia puede parecer plausible al principio, pero P0083R3 , el documento que introdujo lasextractfuncionalidades), tiene una sección dedicada sobre este tema que invalida este argumento:(énfasis mío)
fuente
No creo que eso
const_casty la modificación conduzcan a un comportamiento indefinido en este caso, pero comente si esta argumentación es correcta.Esta respuesta afirma que
Entonces, la línea
v.emplace_back(make_pair(std::move(const_cast<string&>(p.first)),p.second));en la pregunta no conduce a UB si y solo si elstringobjetop.firstno se creó como un objeto constante. Ahora tenga en cuenta que la referencia sobre losextractestadosEntonces, si
extractelnode_handlecorrespondiente ap,pcontinúa viviendo en su ubicación de almacenamiento. Pero después de la extracción, se me permitemoveeliminar los recursospcomo en el código de la respuesta de druckermanly . Esto significa quepy por lo tanto también elstringobjetop.firstfue no creó como objeto const originalmente.Por lo tanto, creo que la modificación de las
mapclaves de 's no conduce a UB y, por la respuesta de Deuchie , parece que también la estructura de árbol ahora corrupta (ahora múltiples claves de cadena vacías)mapno introduce problemas en el destructor. Por lo tanto, el código en la pregunta debería funcionar bien al menos en C ++ 17 dondeextractexiste el método (y la declaración sobre los punteros que siguen siendo válidos).Actualizar
Ahora soy de la opinión de que esta respuesta es incorrecta. No lo estoy eliminando porque está referenciado por otras respuestas.
fuente
std::launder.EDITAR: Esta respuesta es incorrecta. Los comentarios amables han señalado los errores, pero no los estoy eliminando porque se ha mencionado en otras respuestas.
@druckermanly respondió a su primera pregunta, que decía que cambiar las claves
mappor la fuerza rompe el orden en el quemapse construye la estructura interna de datos (árbol Rojo-Negro). Pero es seguro usar elextractmétodo porque hace dos cosas: mover la llave fuera del mapa y luego eliminarlo, para que no afecte el orden del mapa en absoluto.La otra pregunta que planteó, en cuanto a si causaría problemas al deconstruir, no es un problema. Cuando un mapa se deconstruye, llamará al deconstructor de cada uno de sus elementos (mapped_types, etc.), y el
movemétodo garantiza que sea seguro deconstruir una clase después de que se haya movido. Entonces no te preocupes. En pocas palabras, es la operación de lomoveque garantiza que sea seguro eliminar o reasignar algún valor nuevo a la clase "movida". Específicamente parastring, elmovemétodo puede establecer su puntero de caracteres ennullptr, por lo que no elimina los datos reales que se movieron cuando se llamó al deconstructor de la clase original.Un comentario me recordó el punto que estaba pasando por alto, básicamente tenía razón, pero hay una cosa que no estoy totalmente de acuerdo:
const_castprobablemente no sea un UB.constes solo una promesa entre el compilador y nosotros. Los objetos señalados comoconsttodavía son un objeto, igual que los que no lo sonconst, en términos de sus tipos y representaciones en forma binaria. Cuandoconstse descarta, debe comportarse como si fuera una clase mutable normal. Con respecto amove, si desea usarlo, debe pasar un en&lugar de unconst &, por lo que, como veo, no es un UB, simplemente rompe la promesaconsty aleja los datos.También hice dos experimentos, usando MSVC 14.24.28314 y Clang 9.0.0 respectivamente, y arrojaron el mismo resultado.
salida:
Podemos ver que la cadena '2' está vacía ahora, pero obviamente hemos roto el orden del mapa porque la cadena vacía debe reubicarse en el frente. Si intentamos insertar, encontrar o eliminar algunos nodos específicos del mapa, puede causar una catástrofe.
De todos modos, podemos estar de acuerdo en que nunca es una buena idea manipular los datos internos de ninguna clase sin pasar por sus interfaces públicas. El
find,insert,removefunciones, etc. confían su corrección en el orden de la estructura de datos interna, y esa es la razón por la que debe mantenerse alejado de la idea de mirar a escondidas en su interior.fuente
constvalor) ocurrió antes. Sin embargo, el argumento "lamove[función] asegura que sea seguro deconstruir [un objeto de] una clase después de que se haya movido" no se cumple: no se puede mover con seguridad desde unconstobjeto / referencia, ya que eso requiere una modificación, queconstpreviene Puede intentar evitar esa limitación mediante el usoconst_cast, pero en ese punto, en el mejor de los casos, está entrando en un comportamiento específico de implementación profunda, si no UB.movefunción toma una en&lugar de unaconst&, por lo que si uno insiste en que mueve una tecla fuera de un mapa, debe usarlaconst_cast.const_castva a ser desagradable. Puede funcionar la mayor parte del tiempo, pero rompa su código de manera sutil.extract, se nos permite movernos desde el mismo objeto, ¿verdad? (ver mi respuesta)std::launder