Eu sei que at()
é mais lento do que []
por causa de sua verificação de limite, que também é discutida em questões semelhantes como C ++ Vector at / [] operator speed ou :: std :: vector :: at () vs operator [] << resultados surpreendentes !! 5 a 10 vezes mais lento / rápido! . Só não entendo para que at()
serve o método.
Se eu tenho um vetor simples como este: std::vector<int> v(10);
e decido acessar seus elementos usando em at()
vez de []
em situação quando eu tenho um índice i
e não tenho certeza se ele está nos limites de vetores, isso me força a envolvê-lo com try-catch bloco :
try
{
v.at(i) = 2;
}
catch (std::out_of_range& oor)
{
...
}
embora eu seja capaz de obter o mesmo comportamento usando size()
e verificando o índice por conta própria, o que parece mais fácil e muito conveniente para mim:
if (i < v.size())
v[i] = 2;
Portanto, minha pergunta é:
Quais são as vantagens de usar vector :: at em vez de vector :: operator [] ?
Quando devo usar vector :: at em vez de vector :: size + vector :: operator [] ?
if (i < v.size()) v[i] = 2;
, há um caminho de código possível que não é atribuído2
a nenhum elemento dev
. Se esse for o comportamento correto, ótimo. Mas muitas vezes não há nada sensato que essa função possa fazer quandoi >= v.size()
. Portanto, não há nenhuma razão específica para não usar uma exceção para indicar uma situação inesperada. Muitas funções apenas usamoperator[]
sem uma verificação em relação ao tamanho, documento quei
deve estar dentro do intervalo e culpam o UB resultante no chamador.Respostas:
Eu diria que as exceções que
vector::at()
lançam não têm a intenção de serem capturadas pelo código imediatamente circundante. Eles são úteis principalmente para detectar bugs em seu código. Se você precisar verificar os limites no tempo de execução porque, por exemplo, o índice vem da entrada do usuário, é melhor você fazer umaif
instrução. Portanto, em resumo, projete seu código com a intenção devector::at()
nunca lançar uma exceção, de forma que, se isso acontecer, e seu programa abortar, seja um sinal de um bug. (como umassert()
)fonte
size()
+[]
quando o índice depende da entrada do usuário, useassert
em situações onde o índice nunca deve estar fora dos limites para fácil correção de bugs no futuro e.at()
em todas as outras situações (apenas no caso, porque algo errado pode acontecer .. .)vector
então provavelmente é melhor usar isso como a opção "just in case" em vez de emat()
qualquer lugar. Dessa forma, você pode esperar um pouco mais de desempenho no modo de liberação, caso precise.operator[]
, por exemplo, gcc.gnu.org/onlinedocs/libstdc++/manual/… então, se sua plataforma suportar isso, é melhor você ir com ele!Não, não (o bloco try / catch pode ser upstream). É útil quando você deseja que uma exceção seja lançada em vez de seu programa entrar no domínio de comportamento indefinido.
Concordo que a maioria dos acessos fora dos limites a vetores é um erro do programador (nesse caso, você deve usar
assert
para localizar esses erros mais facilmente; a maioria das versões de depuração de bibliotecas padrão fazem isso automaticamente para você). Você não quer usar exceções que podem ser engolidas no upstream para relatar erros do programador: você quer ser capaz de consertar o bug .Como é improvável que um acesso fora dos limites a um vetor faça parte do fluxo normal do programa (caso seja, você está certo: verifique antes com em
size
vez de deixar a exceção borbulhar), concordo com seu diagnóstico:at
é essencialmente inútil.fonte
out_of_range
exceção,abort()
será chamado.try..catch
podem estar presentes no método que está chamando este método.at
é útil na medida em que, de outra forma, você se pegaria escrevendo algo assimif (i < v.size()) { v[i] = 2; } else { throw what_are_you_doing_you_muppet(); }
. As pessoas costumam pensar em funções de lançamento de exceções em termos de "maldições, eu tenho que lidar com a exceção", mas contanto que você documente cuidadosamente o que cada uma de suas funções pode lançar, elas também podem ser usadas como "ótimo, não tem que verificar uma condição e lançar uma exceção ".out_of_range
deriva delogic_error
, e outros programadores "deveriam" saber melhor do que pegarlogic_error
s upstream e ignorá-los.assert
também pode ser ignorado se seus colegas não quiserem saber sobre seus erros, é apenas mais difícil porque eles precisam compilar seu código comNDEBUG
;-) Cada mecanismo tem seus méritos e falhas.O ponto importante aqui é que as exceções permitem a separação do fluxo normal de código da lógica de tratamento de erros, e um único bloco catch pode tratar problemas gerados a partir de uma miríade de sites de lançamento, mesmo se espalhados profundamente nas chamadas de função. Portanto, não
at()
é necessariamente mais fácil para um único uso, mas às vezes se torna mais fácil - e menos ofuscante para a lógica do caso normal - quando você tem muita indexação para validar.Também é digno de nota que, em alguns tipos de código, um índice está sendo incrementado de maneiras complexas e usado continuamente para pesquisar um array. Nesses casos, é muito mais fácil garantir verificações corretas usando
at()
.Como exemplo do mundo real, tenho um código que transforma o C ++ em elementos lexicais e outro código que move um índice sobre o vetor de tokens. Dependendo do que for encontrado, posso desejar incrementar e verificar o próximo elemento, como em:
Nesse tipo de situação, é muito difícil verificar se você chegou ao final da entrada de maneira inadequada, pois isso depende muito dos tokens exatos encontrados. A verificação explícita em cada ponto de uso é dolorosa e há muito mais espaço para erros do programador à medida que incrementos pré / pós, compensações no ponto de uso, raciocínio falho sobre a validade contínua de algum teste anterior etc.
fonte
at
pode ser mais claro se você tiver um ponteiro para o vetor:Desempenho à parte, o primeiro deles é o código mais simples e claro.
fonte
Primeiro, não é especificado se
at()
ouoperator[]
é mais lento. Quando não há erro de limite, eu esperaria que eles tivessem a mesma velocidade, pelo menos em compilações de depuração. A diferença é queat()
especifica exatamente o que vai acontecer se houver um erro de limite (uma exceção), onde, como no caso deoperator[]
, é um comportamento indefinido - uma falha em todos os sistemas que uso (g ++ e VC ++), pelo menos quando os sinalizadores de depuração normais são usados. (Outra diferença é que, depois de ter certeza do meu código, posso obter um aumento substancial de velocidadeoperator[]
desativando a depuração. Se o desempenho exigir - eu não faria isso a menos que fosse necessário.)Na prática,
at()
raramente é apropriado. Se o contexto é tal que você sabe que o índice pode ser inválido, você provavelmente quer o teste explícito (por exemplo, para retornar um valor padrão ou algo assim), e se você sabe que não pode ser inválido, você deseja abortar (e se você não sabe se pode ser inválido ou não, sugiro que especifique a interface de sua função com mais precisão). Existem algumas exceções, no entanto, em que o índice inválido pode resultar da análise de dados do usuário e o erro deve causar o cancelamento de toda a solicitação (mas não interromper o funcionamento do servidor); nesses casos, uma exceção é apropriada eat()
fará isso por você.fonte
operator[]
não é forçado a verificar os limites, mas simat()
? Isso implica problemas de cache, especulação e buffer de ramificação?operator[]
não é necessário para fazer a verificação de limites, mas todas as boas implementações o fazem. Pelo menos no modo de depuração. A única diferença é o que eles fazem se o índice estiver fora dos limites:operator[]
aborta com uma mensagem de erro,at()
lança uma exceção.at()
.std::string
nem sempre funcionava se as opções de verificação não correspondessem às do tempo de execução:-MD
e é melhor você desativar a verificação-MDd
, e é melhor você ter ligado.)O objetivo de usar exceções é que o código de tratamento de erros pode estar mais distante.
Nesse caso específico, a entrada do usuário é de fato um bom exemplo. Imagine que você deseja analisar semanticamente uma estrutura de dados XML que usa índices para se referir a algum tipo de recurso armazenado internamente em a
std::vector
. Agora a árvore XML é uma árvore, então você provavelmente deseja usar recursão para analisá-la. No fundo, na recursão, pode haver uma violação de acesso por parte do gravador do arquivo XML. Nesse caso, você geralmente deseja eliminar todos os níveis de recursão e apenas rejeitar o arquivo inteiro (ou qualquer tipo de estrutura "mais grosseira"). É aqui que o at vem a calhar. Você pode simplesmente escrever o código de análise como se o arquivo fosse válido. O código da biblioteca cuidará da detecção de erros e você pode apenas capturar o erro no nível grosseiro.Além disso, outros contêineres, como
std::map
, também têm ostd::map::at
que tem uma semântica ligeiramente diferente do questd::map::operator[]
: at pode ser usado em um mapa const, enquantooperator[]
não pode. Agora, se você quiser escrever um código agnóstico de contêiner, como algo que pode lidar comconst std::vector<T>&
ouconst std::map<std::size_t, T>&
,ContainerType::at
seria sua arma preferida.No entanto, todos esses casos geralmente aparecem ao lidar com algum tipo de entrada de dados não validada. Se você tiver certeza sobre seu intervalo válido, como normalmente deveria estar, geralmente pode usar
operator[]
, mas melhor ainda, iteradores combegin()
eend()
.fonte
De acordo com este artigo, desempenho à parte, não faz diferença usar
at
ouoperator[]
, apenas se o acesso for garantido estar dentro do tamanho do vetor. Caso contrário, se o acesso for baseado apenas na capacidade do vetor, é mais seguro usarat
.fonte
Observação: parece que algumas pessoas novas estão rejeitando essa resposta sem ter a cortesia de dizer o que está errado. A resposta abaixo está correta e pode ser verificada aqui .
Na verdade, há apenas uma diferença:
at
a verificação de limites, enquantooperator[]
não. Isso se aplica a compilações de depuração, bem como a compilações de lançamento, e isso é muito bem especificado pelos padrões. É simples assim.Isso torna o
at
método mais lento, mas também é um conselho muito ruim para não usarat
. Você tem que olhar para números absolutos, não para números relativos. Posso apostar com segurança que a maior parte do seu código está fazendo operações muito mais caras do queat
. Pessoalmente, tento usarat
porque não quero que um bug desagradável crie um comportamento indefinido e entre furtivamente na produção.fonte
std::out_of_range
ou qualquer forma de arremessostd::logic_error
é, de fato, um erro lógico por si só aqui .at
e[]
e minha resposta simplesmente afirma a diferença. Eu pessoalmente uso o método "seguro" quando o desempenho não é um problema. Como diz Knuth, não faça uma otimização prematura. Além disso, é bom detectar bugs mais cedo do que na produção, independentemente das diferenças filosóficas.