Eu identifiquei quatro maneiras diferentes de inserir elementos em um std::map
:
std::map<int, int> function;
function[0] = 42;
function.insert(std::map<int, int>::value_type(0, 42));
function.insert(std::pair<int, int>(0, 42));
function.insert(std::make_pair(0, 42));
Qual dessas é a forma idiomática preferida? (E há outra maneira que eu não pensei?)
Respostas:
Em primeiro lugar,
operator[]
einsert
funções membro não são funcionalmente equivalentes:operator[]
irá procurar a chave, inserir um valor padrão construído se não for encontrado e retornar uma referência à qual você atribui um valor. Obviamente, isso pode ser ineficiente se omapped_type
puder se beneficiar de ser inicializado diretamente, em vez de construir e atribuir o padrão. Este método também torna impossível determinar se uma inserção realmente ocorreu ou se você apenas substituiu o valor de uma chave inserida anteriormenteinsert
função de membro não terá efeito se a chave já estiver presente no mapa e, embora seja freqüentemente esquecida, retorna umstd::pair<iterator, bool>
que pode ser de interesse (principalmente para determinar se a inserção foi realmente feita).De todas as possibilidades de chamada listadas
insert
, todas as três são quase equivalentes. Como um lembrete, vamos dar uma olhada nainsert
assinatura no padrão:Então, como as três chamadas são diferentes?
std::make_pair
depende da dedução do argumento do modelo e pode (e neste caso irá ) produzir algo de um tipo diferente do que o realvalue_type
do mapa, o que exigirá uma chamada adicional para ostd::pair
construtor do modelo a fim de converter paravalue_type
(ou seja: adicionarconst
afirst_type
)std::pair<int, int>
também exigirá uma chamada adicional para o construtor do modelo destd::pair
para converter o parâmetro paravalue_type
(ou seja: adicionarconst
afirst_type
)std::map<int, int>::value_type
não deixa absolutamente nenhum lugar para dúvidas, pois é diretamente o tipo de parâmetro esperado pelainsert
função de membro.No final, eu evitaria usar
operator[]
quando o objetivo fosse inserir, a menos que não houvesse nenhum custo adicional na construção e atribuição de defaultmapped_type
, e não me importo em determinar se uma nova chave foi efetivamente inserida. Ao usarinsert
, construir umvalue_type
é provavelmente o caminho a percorrer.fonte
A partir do C ++ 11, você tem duas opções adicionais principais. Primeiro, você pode usar
insert()
a sintaxe de inicialização de lista:Isso é funcionalmente equivalente a
mas muito mais conciso e legível. Como outras respostas observaram, isso tem várias vantagens em relação às outras formas:
operator[]
abordagem requer que o tipo mapeado seja atribuível, o que nem sempre é o caso.operator[]
abordagem pode sobrescrever elementos existentes e não oferece nenhuma maneira de saber se isso aconteceu.insert
listadas envolvem uma conversão de tipo implícita, o que pode tornar seu código mais lento.A principal desvantagem é que esse formulário costumava exigir que a chave e o valor fossem copiáveis, portanto, não funcionaria com, por exemplo, um mapa com
unique_ptr
valores. Isso foi corrigido no padrão, mas a correção pode não ter atingido a implementação da biblioteca padrão ainda.Em segundo lugar, você pode usar o
emplace()
método:Isso é mais conciso do que qualquer uma das formas de
insert()
, funciona bem com tipos de movimento apenas comounique_ptr
, e teoricamente pode ser um pouco mais eficiente (embora um compilador decente deva otimizar a diferença). A única grande desvantagem é que pode surpreender um pouco seus leitores, já que osemplace
métodos geralmente não são usados dessa forma.fonte
A primeira versão:
pode ou não inserir o valor 42 no mapa. Se a chave
0
existir, ele atribuirá 42 a essa chave, sobrescrevendo qualquer valor que ela tivesse. Caso contrário, ele insere o par chave / valor.As funções de inserção:
por outro lado, não faça nada se a chave
0
já existir no mapa. Se a chave não existir, ele insere o par chave / valor.As três funções de inserção são quase idênticas.
std::map<int, int>::value_type
é otypedef
parastd::pair<const int, int>
e,std::make_pair()
obviamente, produz umastd::pair<>
mágica de dedução via template. O resultado final, no entanto, deve ser o mesmo para as versões 2, 3 e 4.Qual devo usar? Eu pessoalmente prefiro a versão 1; é conciso e "natural". Claro, se seu comportamento de substituição não for desejado, então eu preferiria a versão 4, uma vez que requer menos digitação do que as versões 2 e 3. Não sei se existe uma única maneira de fato de inserir pares de chave / valor em um
std::map
.Outra maneira de inserir valores em um mapa por meio de um de seus construtores:
fonte
Se você deseja substituir o elemento com a chave 0
De outra forma:
fonte
Já que C ++ 17
std::map
oferece dois novos métodos de inserção:insert_or_assign()
etry_emplace()
, como também mencionado no comentário de sp2danny .insert_or_assign()
Basicamente,
insert_or_assign()
é uma versão "aprimorada" dooperator[]
. Em contraste comoperator[]
,insert_or_assign()
não exige que o tipo de valor do mapa seja construtível por padrão. Por exemplo, o código a seguir não é compilado, porqueMyClass
não tem um construtor padrão:No entanto, se você substituir
myMap[0] = MyClass(1);
pela linha a seguir, o código será compilado e a inserção ocorrerá conforme o esperado:Além disso, semelhante a
insert()
,insert_or_assign()
retorna apair<iterator, bool>
. O valor booleano étrue
se uma inserção ocorreu efalse
se uma atribuição foi feita. O iterador aponta para o elemento que foi inserido ou atualizado.try_emplace()
Semelhante ao anterior,
try_emplace()
é uma "melhoria" deemplace()
. Em contraste comemplace()
,try_emplace()
não modifica seus argumentos se a inserção falhar devido a uma chave já existente no mapa. Por exemplo, o código a seguir tenta substituir um elemento por uma chave que já está armazenada no mapa (consulte *):Saída (pelo menos para VS2017 e Coliru):
Como você pode ver,
pMyObj
não aponta mais para o objeto original. No entanto, se você substituirauto [it, b] = myMap2.emplace(0, std::move(pMyObj));
pelo código a seguir, a saída parecerá diferente, porquepMyObj
permanece inalterada:Resultado:
Código em Coliru
Observação: tentei manter minhas explicações o mais curtas e simples possível para encaixá-las nessa resposta. Para uma descrição mais precisa e abrangente, recomendo a leitura deste artigo sobre Fluent C ++ .
fonte
Tenho feito algumas comparações de tempo entre as versões acima mencionadas:
Acontece que as diferenças de tempo entre as versões de inserção são mínimas.
Isso dá respectivamente para as versões (executei o arquivo 3 vezes, daí as 3 diferenças de tempo consecutivas para cada uma):
2.198 ms, 2.078 ms, 2.072 ms
2.290 ms, 2.037 ms, 2.046 ms
2592 ms, 2278 ms, 2296 ms
2.234 ms, 2.031 ms, 2.027 ms
Conseqüentemente, os resultados entre diferentes versões de inserção podem ser desprezados (embora não execute um teste de hipótese)!
A
map_W_3[it] = Widget(2.0);
versão leva cerca de 10-15% mais tempo para este exemplo devido a uma inicialização com o construtor padrão para Widget.fonte
Em suma, o
[]
operador é mais eficiente para atualizar valores porque envolve chamar o construtor padrão do tipo de valor e, em seguida, atribuir a ele um novo valor, emborainsert()
seja mais eficiente para adicionar valores.O trecho citado de STL Efetivo: 50 Maneiras Específicas de Melhorar Seu Uso da Biblioteca de Modelos Padrão por Scott Meyers, Item 24 pode ajudar.
Você pode decidir escolher uma versão genérica-livre de programação disso, mas o ponto é que eu acho esse paradigma (diferenciar 'adicionar' e 'atualizar') extremamente útil.
fonte
Se você quiser inserir um elemento em std :: map - use a função insert (), e se você quiser encontrar o elemento (por chave) e atribuir algum a ele - use o operador [].
Para simplificar a inserção, use boost :: assign library, assim:
fonte
Acabei de mudar um pouco o problema (mapa de strings) para mostrar outro interesse de insert:
o fato de que o compilador não mostra nenhum erro em "rancking [1] = 42;" pode ter um impacto devastador!
fonte
std::string::operator=(char)
existe, mas mostram um erro para o último porque o construtorstd::string::string(char)
não existe. Não deve produzir um erro porque C ++ sempre interpreta livremente qualquer literal de estilo inteiro comochar
, então este não é um bug do compilador, mas sim um erro do programador. Basicamente, estou apenas dizendo que se isso introduz ou não um bug em seu código, é algo que você deve observar por si mesmo. BTW, você pode imprimirrancking[0]
e um compilador usando ASCII irá imprimir*
, que é(char)(42)
.