No caso particular de um mapa, as opções antigas eram apenas duas: operator[]
e insert
(sabores diferentes de insert
). Então eu vou começar a explicar isso.
O operator[]
é um operador de localização ou adição . Ele tentará encontrar um elemento com a chave especificada dentro do mapa e, se existir, retornará uma referência ao valor armazenado. Caso contrário, ele criará um novo elemento inserido no local com a inicialização padrão e retornará uma referência a ele.
A insert
função (no tipo de elemento único) pega um value_type
( std::pair<const Key,Value>
), usa a chave ( first
membro) e tenta inseri-la. Como std::map
não permite duplicatas, se houver um elemento existente, ele não inserirá nada.
A primeira diferença entre os dois é que operator[]
precisa ser capaz de construir um valor inicial padrão e, portanto, é inutilizável para tipos de valor que não podem ser inicializados por padrão. A segunda diferença entre os dois é o que acontece quando já existe um elemento com a chave fornecida. A insert
função não modifica o estado do mapa, mas retorna um iterador para o elemento (e um false
indicando que não foi inserido).
// assume m is std::map<int,int> already has an element with key 5 and value 0
m[5] = 10; // postcondition: m[5] == 10
m.insert(std::make_pair(5,15)); // m[5] is still 10
No caso do insert
argumento, é um objeto de value_type
, que pode ser criado de diferentes maneiras. Você pode construí-lo diretamente com o tipo apropriado ou passar qualquer objeto a partir do qual ele value_type
possa ser construído, e é aí que std::make_pair
entra em cena, pois permite a criação simples de std::pair
objetos, embora provavelmente não seja o que você deseja ...
O efeito líquido das seguintes chamadas é semelhante :
K t; V u;
std::map<K,V> m; // std::map<K,V>::value_type is std::pair<const K,V>
m.insert( std::pair<const K,V>(t,u) ); // 1
m.insert( std::map<K,V>::value_type(t,u) ); // 2
m.insert( std::make_pair(t,u) ); // 3
Mas não são realmente iguais ... [1] e [2] são realmente equivalentes. Nos dois casos, o código cria um objeto temporário do mesmo tipo ( std::pair<const K,V>
) e o passa para a insert
função. A insert
função criará o nó apropriado na árvore de pesquisa binária e copiará a value_type
parte do argumento para o nó. A vantagem de usar value_type
é que, bem, value_type
sempre corresponde value_type
, você não pode digitar errado o tipo dos std::pair
argumentos!
A diferença está em [3]. A função std::make_pair
é uma função de modelo que criará um std::pair
. A assinatura é:
template <typename T, typename U>
std::pair<T,U> make_pair(T const & t, U const & u );
Não forneci intencionalmente os argumentos do modelo std::make_pair
, pois esse é o uso comum. E a implicação é que os argumentos do modelo são deduzidos da chamada, nesse caso, para que seja T==K,U==V
, portanto a chamada para std::make_pair
retornará a std::pair<K,V>
(observe a falta const
). A assinatura requer value_type
que seja próximo, mas não o mesmo que o valor retornado da chamada para std::make_pair
. Por estar próximo o suficiente, ele criará um temporário do tipo correto e a cópia inicializará. Isso, por sua vez, será copiado para o nó, criando um total de duas cópias.
Isso pode ser corrigido fornecendo os argumentos do modelo:
m.insert( std::make_pair<const K,V>(t,u) ); // 4
Mas isso ainda é propenso a erros da mesma maneira que digitar explicitamente o tipo no caso [1].
Até esse ponto, temos diferentes maneiras de chamar insert
que exigem a criação value_type
externa e a cópia desse objeto no contêiner. Como alternativa, você pode usar operator[]
se o tipo é padrão construtível e atribuível (focalizando intencionalmente apenas em m[k]=v
) e requer a inicialização padrão de um objeto e a cópia do valor nesse objeto.
Em C ++ 11, com modelos variádicos e encaminhamento perfeito há uma nova maneira de adicionar elementos dentro de um recipiente por meio de emplacing (criando no lugar). As emplace
funções nos diferentes contêineres fazem basicamente a mesma coisa: em vez de obter uma fonte da qual copiar no contêiner, a função usa os parâmetros que serão encaminhados ao construtor do objeto armazenado no contêiner.
m.emplace(t,u); // 5
Em [5], o std::pair<const K, V>
não é criado e passado para emplace
, mas as referências ao objeto t
e u
são passadas para emplace
que os encaminha ao construtor do value_type
subobjeto dentro da estrutura de dados. Nesse caso, nenhuma cópia std::pair<const K,V>
é feita, o que é a vantagem das emplace
alternativas C ++ 03. Como no caso insert
, não substituirá o valor no mapa.
Uma pergunta interessante sobre a qual eu não havia pensado é como emplace
realmente pode ser implementado para um mapa, e esse não é um problema simples no caso geral.