Em C ++, não há problema em roubar recursos de um mapa que eu não preciso mais mais? Mais precisamente, suponha que eu possua um std::map
com std::string
chaves e que eu queira construir um vetor roubando os recursos das map
chaves s usando std::move
. Observe que esse acesso de gravação às chaves corrompe a estrutura de dados interna (ordenação de chaves) da map
mas não a usarei posteriormente.
Pergunta : Posso fazer isso sem problemas ou isso levará a erros inesperados, por exemplo, no destruidor do, map
porque eu o acessei de uma maneira que std::map
não foi planejada?
Aqui está um programa de exemplo:
#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;
}
Produz a saída:
key before stealing: aLongString
key after stealing:
key before stealing: anotherLongString
key after stealing:
EDIT: Eu gostaria de fazer isso para todo o conteúdo de um mapa grande e salvar alocações dinâmicas por esse roubo de recurso.
c++
move-semantics
phinz
fonte
fonte
const
valor é sempre UB.std::string
tem otimização de cadeia curta. Isso significa que existe uma lógica não trivial em copiar e mover, e não apenas troca de ponteiros e, além disso, na maioria das vezes a mudança implica em copiar - para que você não lide com seqüências longas. De qualquer forma, a diferença estatística era pequena e, em geral, varia de acordo com o tipo de processamento de string.Respostas:
Você está fazendo um comportamento indefinido, usando
const_cast
para modificar umaconst
variável. Não faça isso. A razãoconst
é que os mapas são classificados por suas chaves. Portanto, modificar uma chave no local está quebrando a suposição subjacente na qual o mapa é construído.Você nunca deve usar
const_cast
para removerconst
de uma variável e modificar essa variável.Dito isto, o C ++ 17 tem a solução para o seu problema:
std::map
aextract
função:E não
using namespace std;
. :)fonte
map
, não vou reclamar se eu não chamar seus métodos (o que não faço) e talvez a ordem interna não seja importante em seu destruidor?const
. A mutação de umconst
objeto é um UB instantâneo, independentemente de alguma coisa realmente ser acessada posteriormente.extract
ao usar iteradores, o argumento amortiza a complexidade constante. Algumas despesas gerais são inevitáveis, mas provavelmente não serão significativas o suficiente para importar. Se você tiver requisitos especiais não cobertos por isso, precisará implementar seu própriomap
atendimento a esses requisitos. Osstd
contêineres destinam-se a aplicações de uso geral comum e não são otimizados para casos de uso específicos.const
? Nesse caso, você pode indicar onde minha argumentação está errada.Seu código tenta modificar
const
objetos, por isso tem um comportamento indefinido, como a resposta de druckermanly aponta corretamente.Algumas outras respostas ( phinz e Deuchie's ) argumentam que a chave não deve ser armazenada como um
const
objeto, porque o identificador do nó resultante da extração de nós fora do mapa permite o nãoconst
acesso à chave. Essa inferência pode parecer plausível a princípio, mas P0083R3 , o artigo que introduziu asextract
funcionalidades), possui uma seção dedicada sobre esse tópico que invalida esse argumento:(ênfase minha)
fonte
Eu não acho que isso
const_cast
e modificação levem a um comportamento indefinido neste caso, mas por favor, comente se essa argumentação está correta.Esta resposta afirma que
Portanto, a linha
v.emplace_back(make_pair(std::move(const_cast<string&>(p.first)),p.second));
na pergunta não leva ao UB se e somente se ostring
objetop.first
não foi criado como um objeto const. Agora observe que a referência sobreextract
estadosPortanto, se eu
extract
onode_handle
correspondente ap
,p
continuar vivendo no seu local de armazenamento. Mas, após a extração, tenho permissão paramove
afastar os recursos dop
código da resposta de druckermanly . Isso significa quep
e, portanto, também ostring
objeto nãop.first
foi criado como objeto const originalmente.Portanto, acho que a modificação das
map
chaves do não leva ao UB e, a partir da resposta de Deuchie , parece que também a estrutura de árvore agora corrompida (agora várias mesmas chaves de cadeia vazias) domap
não introduz problemas no destruidor. Portanto, o código na pergunta deve funcionar bem, pelo menos no C ++ 17, onde oextract
método existe (e a declaração sobre ponteiros permanecendo válidos).Atualizar
Agora sou da opinião de que esta resposta está errada. Não estou excluindo porque é referenciado por outras respostas.
fonte
std::launder
.EDIT: Esta resposta está errada. Comentários gentis apontaram os erros, mas não o excluo porque foi mencionado em outras respostas.
A @druckermanly respondeu à sua primeira pergunta, que dizia que alterar as chaves
map
pela força quebra a ordem em quemap
a estrutura de dados interna é construída (árvore Vermelho-Preto). Mas é seguro usar oextract
método porque ele faz duas coisas: mova a chave para fora do mapa e exclua-a, para que não afete a ordem do mapa.A outra pergunta que você fez, sobre se causaria problemas ao desconstruir, não é um problema. Quando um mapa desconstrói, ele chama o desconstrutor de cada um de seus elementos (mapped_types etc.), e o
move
método garante que seja seguro desconstruir uma classe depois que ela for movida. Então não se preocupe. Em poucas palavras, é a operaçãomove
que garante que é seguro excluir ou reatribuir algum novo valor à classe "movida". Especificamente parastring
, omove
método pode definir seu ponteiro de char paranullptr
, para não excluir os dados reais que foram movidos quando o desconstrutor da classe original foi chamado.Um comentário me lembrou o ponto que eu estava ignorando, basicamente ele estava certo, mas há uma coisa que eu não concordo totalmente:
const_cast
provavelmente não é um UB.const
é apenas uma promessa entre o compilador e nós. objetos anotados comoconst
ainda são um objeto, igual aos que não o sãoconst
, em termos de seus tipos e representações na forma binária. Quando oconst
é descartado, ele deve se comportar como se fosse uma classe mutável normal. No que diz respeito amove
, se você quiser usá-lo, precisará passar um em&
vez de umconst &
, então, como vejo que não é um UB, ele simplesmente quebra a promessa deconst
e afasta os dados.Também fiz dois experimentos, usando o MSVC 14.24.28314 e o Clang 9.0.0, respectivamente, e eles produziram o mesmo resultado.
resultado:
Podemos ver que a string '2' está vazia agora, mas obviamente quebramos a ordem do mapa porque a string vazia deve ser recolocada na frente. Se tentarmos inserir, localizar ou excluir alguns nós específicos do mapa, isso pode causar uma catástrofe.
De qualquer forma, podemos concordar que nunca é uma boa idéia manipular os dados internos de qualquer classe ignorando suas interfaces públicas. A
find
,insert
,remove
funções e assim por diante confiar sua correção na ordem da estrutura de dados interna, e essa é a razão por que devemos ficar longe do pensamento de espreitar para dentro.fonte
const
valor) ocorreu anteriormente. No entanto, o argumento "amove
[função] garante que seja seguro desconstruir [um objeto de] uma classe depois que ela foi movida" não é válido: você não pode mover-se com segurança de umconst
objeto / referência, pois isso requer modificação, o que requer modificações.const
impede. Você pode tentar contornar essa limitação usandoconst_cast
, mas nesse momento você está, na melhor das hipóteses, entrando em um comportamento específico de implementação profunda, se não UB.move
função aceita um em&
vez de umconst&
, portanto, se alguém insistir em que ele move uma chave de um mapa, ele deve usarconst_cast
.const_cast
será desagradável. Pode funcionar na maioria das vezes, mas quebre seu código de maneiras sutis.extract
, depois , podemos nos mover do mesmo objeto, certo? (veja minha resposta)std::launder