Eu sou um programador de C ++ com experiência limitada.
Supondo que eu queira usar um STL map
para armazenar e manipular alguns dados, gostaria de saber se há alguma diferença significativa (também no desempenho) entre essas duas abordagens da estrutura de dados:
Choice 1:
map<int, pair<string, bool> >
Choice 2:
struct Ente {
string name;
bool flag;
}
map<int, Ente>
Especificamente, existe alguma sobrecarga usando um em struct
vez de um simples pair
?
c++
data-structures
stl
Marco Stramezzi
fonte
fonte
std::pair
é uma estrutura.std::pair
é um modelo .std::pair<string, bool>
é uma estrutura.pair
é totalmente desprovido de semântica. Ninguém lendo seu código (incluindo você no futuro) saberá que essee.first
é o nome de algo, a menos que você o indique explicitamente. Acredito firmemente que issopair
foi uma adição muito pobre e preguiçosastd
, e que, quando foi concebido, ninguém pensou "mas algum dia, todo mundo vai usar isso para tudo o que é duas coisas, e ninguém saberá o que o código de alguém significa" "map
iteradores não sejam exceções válidas. ( "primeiro" = chave e "segundo" = valor ... realmente,std
realmente?)Respostas:
A opção 1 é válida para pequenas coisas "usadas apenas uma vez". Essencialmente
std::pair
ainda é uma estrutura. Como afirmado por esse comentário, a escolha 1 levará a um código realmente feio em algum lugar abaixo da toca do coelhothing.second->first.second->second
e ninguém realmente quer decifrar isso.A opção 2 é melhor para todo o resto, porque é mais fácil ler qual é o significado das coisas no mapa. Também é mais flexível se você deseja alterar os dados (por exemplo, quando o Ente precisa de outro sinalizador). O desempenho não deve ser um problema aqui.
fonte
Desempenho :
Depende.
No seu caso particular, não haverá diferença de desempenho, pois os dois serão dispostos da mesma forma na memória.
Em um caso muito específico (se você estivesse usando uma estrutura vazia como um dos membros dos dados), o
std::pair<>
potencial poderia fazer uso da otimização da base vazia (EBO) e ter um tamanho menor que o equivalente à estrutura. E tamanho menor geralmente significa maior desempenho:Impressões: 32, 32, 40, 40 em ideone .
Nota: Não conheço nenhuma implementação que realmente use o truque EBO para pares regulares, no entanto, geralmente é usado para tuplas.
Legibilidade :
Além das micro-otimizações, no entanto, uma estrutura nomeada é mais ergonômica.
Quero dizer,
map[k].first
não é tão ruim assim tãoget<0>(map[k])
pouco inteligível. Contraste com omap[k].name
qual indica imediatamente o que estamos lendo.É ainda mais importante quando os tipos são conversíveis entre si, pois trocá-los inadvertidamente se torna uma preocupação real.
Você também pode querer ler sobre Estrutural versus Tipagem Nominal.
Ente
é um tipo específico que só pode ser operado por coisas que esperamosEnte
, qualquer coisa que possa operarstd::pair<std::string, bool>
pode operar com elas ... mesmo quando o contémstd::string
oubool
não o que eles esperam, porquestd::pair
não possui semântica associada a ele.Manutenção :
Em termos de manutenção,
pair
é o pior. Você não pode adicionar um campo.tuple
feiras melhor nesse sentido, desde que você acrescente o novo campo, todos os campos existentes ainda serão acessados pelo mesmo índice. O que é tão inescrutável quanto antes, mas pelo menos você não precisa atualizá-los.struct
é o vencedor claro. Você pode adicionar campos onde quiser.Em conclusão:
pair
é o pior dos dois mundos,tuple
pode ter uma ligeira aresta em um caso muito específico (tipo vazio),struct
.Nota: se você usa getters, pode usar o truque de base vazia sem que os clientes tenham que saber sobre isso como em
struct Thing: Empty { std::string name; }
; é por isso que o encapsulamento é o próximo tópico com o qual você deve se preocupar.fonte
first
esecond
, não há lugar para a Otimização da Base Vazia entrar em ação.pair
que usaria EBO, mas um compilador poderia fornecer um built-in. Como esses membros devem ser membros, no entanto, pode ser observável (verificando seus endereços, por exemplo), caso em que não estaria em conformidade.[[no_unique_address]]
, o que permite o equivalente a EBO para membros.O par brilha mais quando usado como o tipo de retorno de uma função, juntamente com a atribuição desestruturada usando std :: tie e a ligação estruturada do C ++ 17. Usando std :: tie:
Usando a ligação estruturada do C ++ 17:
Um mau exemplo de uso de um std :: pair (ou tupla) seria algo como isto:
porque não está claro ao chamar std :: get <2> (player_data) o que está armazenado no índice de posição 2. Lembre-se da legibilidade e deixe óbvio para o leitor o que o código está fazendo é importante . Considere que isso é muito mais legível:
Em geral, você deve pensar em std :: pair e std :: tuple como formas de retornar mais de 1 objeto de uma função. A regra geral que eu uso (e já vi muitos outros também) é que os objetos retornados em um std :: tuple ou std :: pair são apenas "relacionados" no contexto de fazer uma chamada para uma função que os retorna ou no contexto da estrutura de dados que os vincula (por exemplo, std :: map usa std :: pair para seu tipo de armazenamento). Se o relacionamento existir em outro lugar no seu código, você deve usar uma estrutura.
Seções relacionadas das Diretrizes Principais:
fonte