Por que usar iteradores em vez de índices de matriz?

239

Pegue as duas linhas de código a seguir:

for (int i = 0; i < some_vector.size(); i++)
{
    //do stuff
}

E isto:

for (some_iterator = some_vector.begin(); some_iterator != some_vector.end();
    some_iterator++)
{
    //do stuff
}

Disseram-me que a segunda maneira é preferida. Por que exatamente é isso?

Jason Baker
fonte
72
A segunda maneira preferida é mudar some_iterator++para ++some_iterator. O pós-incremento cria um iterador temporário desnecessário.
jason
6
Você também deve incluir end()a cláusula de declaração.
Lightness Races in Orbit -
5
@ Tomlak: qualquer pessoa que use uma implementação C ++ com um ineficiente vector::endprovavelmente tem problemas piores com que se preocupar do que se é extraído de loops ou não. Pessoalmente, prefiro a clareza - se fosse uma ligação findna condição de término, eu me preocuparia.
Steve Jessop
13
@Tomalak: Esse código não é desleixado (bem, talvez o pós-incremento), é conciso e claro, na medida em que os iteradores C ++ permitem concisão. Adicionar mais variáveis ​​adiciona esforço cognitivo em prol de uma otimização prematura. Isso é desleixado.
Steve Jessop 31/05
7
@ Tomlak: é prematuro se não for um gargalo. Seu segundo ponto me parece absurdo, já que a comparação correta não é entre it != vec.end()e it != end, é entre (vector<T>::iterator it = vec.begin(); it != vec.end(); ++it)e (vector<T>::iterator it = vec.begin(), end = vec.end(); it != end; ++it). Não preciso contar os personagens. Por todos os meios, prefira um sobre o outro, mas o desacordo de outras pessoas com a sua preferência não é "negligência", é uma preferência por código mais simples com menos variáveis ​​e, portanto, menos em que pensar ao lê-lo.
Steve Jessop

Respostas:

210

A primeira forma é eficiente apenas se vector.size () for uma operação rápida. Isso vale para vetores, mas não para listas, por exemplo. Além disso, o que você planeja fazer dentro do corpo do loop? Se você planeja acessar os elementos como em

T elem = some_vector[i];

então você está assumindo que o contêiner operator[](std::size_t)definiu. Novamente, isso é verdade para vetores, mas não para outros contêineres.

O uso de iteradores aproxima você da independência do contêiner . Você não está fazendo suposições sobre a capacidade de acesso aleatório ou size()operação rápida , apenas que o contêiner possui recursos de iterador.

Você pode aprimorar ainda mais seu código usando algoritmos padrão. Dependendo do que é que você está tentando alcançar, você pode optar por usar std::for_each(), std::transform()e assim por diante. Ao usar um algoritmo padrão em vez de um loop explícito, você evita reinventar a roda. É provável que seu código seja mais eficiente (dado que o algoritmo correto foi escolhido), correto e reutilizável.

wilhelmtell
fonte
8
Além disso, você esqueceu que os iteradores podem executar tarefas como fail-fast, de modo que, se houver uma modificação simultânea na estrutura que você está acessando, você saberá sobre isso. Você não pode fazer isso apenas com um número inteiro.
Marcin
4
Isso me confunde: "Isso é verdade para vetores, mas não para listas, por exemplo." Por quê? Qualquer pessoa com um cérebro manterá uma size_tvariável de membro acompanhando size().
GManNickG
19
@GMan - em quase todas as implementações, size () é rápido para listas, assim como para vetores. A próxima versão do padrão exigirá que isso seja verdade. O verdadeiro problema é a lentidão da recuperação por posição.
11119 Daniel Earwicker
8
@ GMan: Para armazenar o tamanho da lista, é necessário que o fatiamento e a emenda da lista sejam O (n) em vez de O (1).
5
No C ++ 0x, a size()função de membro precisará ter uma complexidade de tempo constante para todos os contêineres que a suportam, inclusive std::list.
James McNellis
54

Faz parte do processo moderno de doutrinação em C ++. Os iteradores são a única maneira de iterar a maioria dos contêineres; portanto, você o usa mesmo com vetores apenas para entrar na mentalidade adequada. Sério, é a única razão pela qual faço isso - acho que nunca substituí um vetor por um tipo diferente de contêiner.


Uau, isso ainda está sendo votado depois de três semanas. Eu acho que não vale a pena ser um pouco irônico.

Eu acho que o índice da matriz é mais legível. Ele corresponde à sintaxe usada em outros idiomas e à sintaxe usada para matrizes C à moda antiga. Também é menos detalhado. A eficiência deve ser uma lavagem, se o seu compilador for bom, e quase não há casos em que isso seja importante.

Mesmo assim, continuo usando iteradores frequentemente com vetores. Eu acredito que o iterador é um conceito importante, então eu o promovo sempre que posso.

Mark Ransom
fonte
1
O iterador C ++ também é horrivelmente quebrado conceitualmente. Para vetores, eu acabei de ser pego porque o ponteiro final é final final + 1 (!). Para fluxos, o modelo do iterador é apenas surreal - um token imaginário que não existe. Da mesma forma para listas vinculadas. O paradigma só faz sentido para matrizes e depois não muito. Por que eu preciso de dois iterador objeto, não apenas um ...
Tuntable
5
@aberglas eles não estão estragados, você não está acostumado a eles, e é por isso que eu defendo usá-los mesmo quando você não precisa! Intervalos semi-abertos são um conceito comum, e os sentinelas que nunca devem ser acessados ​​diretamente são tão antigos quanto a própria programação.
Mark Ransom
4
dê uma olhada nos iteradores de fluxo e pense no que == foi pervertido para fazer para se ajustar ao padrão e depois me diga que os iteradores não estão quebrados! Ou para listas vinculadas. Mesmo para matrizes, ter que especificar um passado até o final é uma ideia quebrada do estilo C - apontador para o nunca, nunca. Eles devem ser como Java ou C # ou qualquer iterador de qualquer outra linguagem, com um iterador necessário (em vez de dois objetos) e um teste final simples.
Tuntable
53

porque você não está vinculando seu código à implementação específica da lista some_vector. se você usar índices de matriz, deve ser alguma forma de matriz; se você usar iteradores, poderá usar esse código em qualquer implementação de lista.

cruizer
fonte
23
A interface std :: list intencionalmente não oferece o operador [] (size_t n) porque seria O (n).
MSalters 26/09/08
33

Imagine que algum_vector seja implementado com uma lista vinculada. A solicitação de um item no i-ésimo lugar exige que sejam executadas i operações para percorrer a lista de nós. Agora, se você usar o iterador, em geral, fará o possível para ser o mais eficiente possível (no caso de uma lista vinculada, ele manterá um ponteiro para o nó atual e o avançará em cada iteração, exigindo apenas uma operação única).

Portanto, fornece duas coisas:

  • Abstração de uso: você apenas deseja iterar alguns elementos, não se importa com como fazê-lo
  • atuação
asterita
fonte
1
"ele manterá um ponteiro para o nó atual e o aprimorará [coisas boas sobre eficiência]" - sim, eu não entendo por que as pessoas têm problemas para entender o conceito de iteradores. conceitualmente, são apenas um superconjunto de indicadores. por que calcular o deslocamento de algum elemento repetidamente quando você pode apenas armazenar em cache um ponteiro para ele? bem, é isso que os iteradores também fazem.
underscore_d
27

Eu serei o advogado do diabo aqui, e não recomendo iteradores. A principal razão pela qual, é todo o código-fonte em que trabalhei, desde o desenvolvimento de aplicativos da área de trabalho até o desenvolvimento de jogos, nem tenho que usar iteradores. O tempo todo eles não são necessários e, em segundo lugar, as suposições ocultas, os códigos e os pesadelos de depuração que você obtém com os iteradores fazem deles um excelente exemplo para não usá-lo em aplicativos que exijam velocidade.

Mesmo do ponto de vista da manutenção, eles são uma bagunça. Não é por causa deles, mas por causa de todo o aliasing que acontece nos bastidores. Como sei que você não implementou seu próprio vetor virtual ou lista de matrizes que faz algo completamente diferente dos padrões. Eu sei que tipo é atualmente atualmente durante o tempo de execução? Você sobrecarregou um operador? Não tive tempo de verificar todo o seu código-fonte. Inferno, eu sei mesmo qual versão do STL você está usando?

O próximo problema que você teve com os iteradores é a abstração com vazamento, embora existam vários sites que discutem isso em detalhes com eles.

Desculpe, ainda não vi nenhum ponto nos iteradores. Se eles abstraem a lista ou o vetor de você, quando na verdade você já deve saber com qual vetor ou lista está lidando, se não o fizer, estará se preparando para algumas ótimas sessões de depuração no futuro.

Chade
fonte
23

Você pode usar um iterador se quiser adicionar / remover itens ao vetor enquanto estiver iterando sobre ele.

some_iterator = some_vector.begin(); 
while (some_iterator != some_vector.end())
{
    if (/* some condition */)
    {
        some_iterator = some_vector.erase(some_iterator);
        // some_iterator now positioned at the element after the deleted element
    }
    else
    {
        if (/* some other condition */)
        {
            some_iterator = some_vector.insert(some_iterator, some_new_value);
            // some_iterator now positioned at new element
        }
        ++some_iterator;
    }
}

Se você estivesse usando índices, teria que embaralhar os itens para cima / baixo na matriz para manipular as inserções e exclusões.

Brian Matthews
fonte
3
se você deseja inserir elementos no meio do contêiner, talvez um vetor não seja uma boa opção de contêiner para começar. é claro, voltamos ao motivo pelo qual os iteradores são legais; é trivial mudar para uma lista.
wilhelmtell
A iteração sobre todos os elementos é muito cara em std::listcomparação a std::vector, no entanto, se você estiver recomendando o uso de uma lista vinculada em vez de a std::vector. Consulte a página 43: ecn.channel9.msdn.com/events/GoingNative12/GN12Cpp11Style.pdf Na minha experiência, descobri que um std::vectoré mais rápido que um, std::listmesmo que esteja pesquisando sobre tudo e removendo elementos em posições arbitrárias.
David Stone
Os índices são estáveis, portanto, não vejo o que é necessário embaralhar adicional para inserções e exclusões.
musiphil
... E com uma lista vinculada - que é o que deve ser usado aqui - sua instrução de loop seria for (node = list->head; node != NULL; node = node->next)mais curta do que suas duas primeiras linhas de código juntas (declaração e cabeçalho de loop). Então eu digo novamente - não há muita diferença fundamental na brevidade entre usar iteradores e não usá-los - você ainda satisfaz as três partes de uma fordeclaração, mesmo se usar while: declare, repita, verifique a terminação.
Engenheiro de
16

Separação de preocupações

É muito bom separar o código de iteração da preocupação principal do loop. É quase uma decisão de design.

De fato, a iteração por índice vincula você à implementação do contêiner. Solicitar ao contêiner um iterador inicial e final, habilita o código do loop para uso com outros tipos de contêineres.

Além disso, std::for_eachvocê diz à coleção o que fazer, em vez de perguntar algo sobre seus componentes internos.

O padrão 0x introduzirá fechamentos, o que tornará essa abordagem muito mais fácil de usar - veja o poder expressivo de, por exemplo, o Ruby [1..6].each { |i| print i; }...

atuação

Mas talvez uma questão muito supervisionada seja a de que, usando a for_eachabordagem, haja uma oportunidade de paralelizar a iteração - os blocos de encadeamento de intel podem distribuir o bloco de código pelo número de processadores no sistema!

Nota: depois de descobrir a algorithmsbiblioteca e, principalmente foreach, passei dois ou três meses escrevendo estruturas ridiculamente pequenas de operadores 'auxiliares' que enlouquecerão seus colegas desenvolvedores. Após esse período, voltei a uma abordagem pragmática - pequenos corpos em loop não merecem foreachmais :)

Uma referência de leitura obrigatória nos iteradores é o livro "Extended STL" .

O GoF possui um pequeno parágrafo no final do padrão Iterator, que fala sobre esse tipo de iteração; é chamado de 'iterador interno'. Dê uma olhada aqui também.

xtofl
fonte
15

Porque é mais orientado a objetos. se você estiver iterando com um índice, estará assumindo:

a) que esses objetos são ordenados
b) que esses objetos podem ser obtidos por um índice
c) que o incremento do índice atingirá todos os itens
d) que esse índice comece em zero

Com um iterador, você está dizendo "me dê tudo para que eu possa trabalhar com ele" sem saber qual é a implementação subjacente. (Em Java, há coleções que não podem ser acessadas por meio de um índice)

Além disso, com um iterador, não é necessário se preocupar em sair dos limites da matriz.

cynicalman
fonte
2
Não acho que "orientado a objetos" seja o termo correto. Os iteradores não são "orientados a objetos" no design. Eles promovem a programação funcional mais do que a programação orientada a objetos, porque incentivam a separação dos algoritmos das classes.
wilhelmtell
Além disso, os iteradores não ajudam a evitar ficar fora dos limites. Algoritmos padrão funcionam, mas os iteradores, por si só, não.
wilhelmtell 25/09/08
É justo o suficiente, @wilhelmtell, estou obviamente pensando nisso do ponto de vista centralizado em Java.
cynicalman
1
E acho que promove o OO, porque está separando as operações nas coleções da implementação dessa coleção. Uma coleção de objetos não deve necessariamente saber quais algoritmos devem ser usados ​​para trabalhar com eles.
cynicalman
Na verdade, existem versões do STL por aí que verificaram os iteradores, o que significa que ele lançará algum tipo de exceção fora dos limites quando você tentar fazer algo com esse iterador.
Daemin 25/09/08
15

Outra coisa interessante dos iteradores é que eles permitem que você expresse (e imponha) sua preferência constante. Este exemplo garante que você não alterará o vetor no meio do seu loop:


for(std::vector<Foo>::const_iterator pos=foos.begin(); pos != foos.end(); ++pos)
{
    // Foo & foo = *pos; // this won't compile
    const Foo & foo = *pos; // this will compile
}
Pat Notz
fonte
Parece razoável, mas ainda duvido que seja esse o motivo const_iterator. Se eu altero o vetor no loop, faço-o por uma razão e, por 99,9% do tempo, alterar não é um acidente e, pelo resto, é apenas um bug como qualquer tipo de erro no código do autor precisa consertar. Como em Java e em muitas outras linguagens, não há nenhum objeto const, mas os usuários dessas linguagens nunca têm um problema sem suporte const nessas linguagens.
Neevek
2
@neevek Se esse não é o motivo const_iterator, então qual poderia ser o motivo?
Underscore_d
@underscore_d, também estou pensando. Eu não sou especialista nisso, é apenas que a resposta não é convincente para mim.
11337 neevek
15

Além de todas as outras excelentes respostas ... intpode não ser grande o suficiente para o seu vetor. Em vez disso, se você deseja usar a indexação, use o size_typepara seu contêiner:

for (std::vector<Foo>::size_type i = 0; i < myvector.size(); ++i)
{
    Foo& this_foo = myvector[i];
    // Do stuff with this_foo
}
Pat Notz
fonte
1
@ Pat Notz, esse é um ponto muito bom. Durante a portabilidade de um aplicativo Windows baseado em STL para x64, tive que lidar com centenas de avisos sobre a atribuição de size_t a um int, possivelmente causando truncamento.
bk1e 25/09/08
1
Sem mencionar o fato de que os tipos de tamanho são sem sinal e int é assinado, então você tem não-intuitivas, conversões bug-esconderijos acontecendo apenas para comparar int ia myvector.size().
Adrian McCarthy
12

Eu provavelmente devo salientar que você também pode ligar

std::for_each(some_vector.begin(), some_vector.end(), &do_stuff);

MSalters
fonte
7

Os iteradores STL estão principalmente lá, de modo que os algoritmos STL, como sort, podem ser independentes de contêineres.

Se você quiser fazer um loop sobre todas as entradas em um vetor, use o estilo de loop de índice.

É menos digitado e mais fácil de analisar para a maioria dos humanos. Seria bom se o C ++ tivesse um loop foreach simples sem exagerar na mágica de modelos.

for( size_t i = 0; i < some_vector.size(); ++i )
{
   T& rT = some_vector[i];
   // now do something with rT
}
'
Jeroen Dirks
fonte
5

Eu não acho que faz muita diferença para um vetor. Eu prefiro usar um índice, pois considero mais legível e você pode acessar aleatoriamente, como pular para a frente 6 itens ou pular para trás, se necessário.

Também gosto de fazer uma referência ao item dentro do loop dessa maneira, para que não haja muitos colchetes ao redor do local:

for(size_t i = 0; i < myvector.size(); i++)
{
    MyClass &item = myvector[i];

    // Do stuff to "item".
}

Usar um iterador pode ser bom se você achar que poderá precisar substituir o vetor por uma lista em algum momento no futuro e também parecer mais elegante para os malucos do STL, mas não consigo pensar em nenhum outro motivo.

Adam Pierce
fonte
a maioria dos algoritmos opera uma vez em cada elemento de um contêiner, sequencialmente. É claro que existem exceções nas quais você deseja percorrer uma coleção de uma maneira ou ordem específica, mas nesse caso eu tentaria escrever um algoritmo que se integra ao STL e que funciona com iteradores.
wilhelmtell 25/09/08
Isso incentivaria a reutilização e evitaria erros de um por um mais tarde. Eu chamaria esse algoritmo como qualquer outro algoritmo padrão, com iteradores.
wilhelmtell
1
Nem precisa de avanço (). O iterador possui os mesmos operadores + = e - = que um índice (para contêineres vetoriais e similares a vetores).
MSalters 26/09/08
I prefer to use an index myself as I consider it to be more readablesomente em algumas situações; em outros, os índices rapidamente ficam muito confusos. and you can do random accessque não é um recurso exclusivo dos índices: consulte en.cppreference.com/w/cpp/concept/RandomAccessIterator
underscore_d
3

A segunda forma representa o que você está fazendo com mais precisão. No seu exemplo, você realmente não se importa com o valor de i - tudo o que você quer é o próximo elemento no iterador.

Colen
fonte
3

Depois de ter aprendido um pouco mais sobre o assunto desta resposta, percebo que foi um pouco simplista demais. A diferença entre este loop:

for (some_iterator = some_vector.begin(); some_iterator != some_vector.end();
    some_iterator++)
{
    //do stuff
}

E esse loop:

for (int i = 0; i < some_vector.size(); i++)
{
    //do stuff
}

É bastante mínimo. De fato, a sintaxe de fazer loops dessa maneira parece estar crescendo em mim:

while (it != end){
    //do stuff
    ++it;
}

Os iteradores desbloqueiam alguns recursos declarativos bastante poderosos e, quando combinados com a biblioteca de algoritmos STL, você pode fazer algumas coisas bem legais que estão fora do escopo da administração de índices de matriz.

Jason Baker
fonte
A verdade é que, se todos os iteradores fossem tão compactos quanto o seu exemplo final, imediatamente, eu teria pouco problema com eles. Obviamente, isso é realmente igual a for (Iter it = {0}; it != end; ++it) {...}- você deixou de fora a declaração -, portanto, a brevidade não é muito diferente do seu segundo exemplo. Ainda assim, +1.
Engenheiro de
3

A indexação requer uma muloperação extra . Por exemplo, para vector<int> v, o compilador é convertido v[i]em &v + sizeof(int) * i.

Marc Eaddy
fonte
Provavelmente não é uma desvantagem significativa em relação aos iteradores na maioria dos casos, mas é uma coisa boa a ter em atenção.
Nobar
3
Para acessos isolados de elemento único, provavelmente. Mas se estamos falando de loops - como o OP -, tenho certeza de que essa resposta é baseada em um compilador imaginário que não otimiza. Qualquer meio decente terá ampla oportunidade e probabilidade de armazená-lo em cache sizeofe apenas adicioná-lo uma vez por iteração, em vez de fazer todo o cálculo do deslocamento novamente todas as vezes.
underscore_d
2

Durante a iteração, você não precisa saber o número de itens a serem processados. Você só precisa do item e os iteradores fazem essas coisas muito bem.

Sergey Stolyarov
fonte
2

Ninguém mencionou ainda que uma vantagem dos índices é que eles não se tornam inválidos quando você anexa a um contêiner contíguo como std::vector, portanto, você pode adicionar itens ao contêiner durante a iteração.

Isso também é possível com os iteradores, mas você deve ligar reserve()e, portanto, precisa saber quantos itens anexará.

danpla
fonte
1

Vários bons pontos já. Tenho alguns comentários adicionais:

  1. Supondo que estamos falando sobre a biblioteca padrão C ++, "vetor" implica em um contêiner de acesso aleatório que possui as garantias da matriz C (acesso aleatório, layout de memória contíguo etc.). Se você tivesse dito 'some_container', muitas das respostas acima teriam sido mais precisas (independência do contêiner etc.).

  2. Para eliminar quaisquer dependências na otimização do compilador, você pode mover some_vector.size () para fora do loop no código indexado, da seguinte maneira:

    const size_t numElems = some_vector.size ();
    para (size_t i = 0; i 
  3. Sempre pré-incremente os iteradores e trate os pós-incrementos como casos excepcionais.

for (some_iterator = some_vector.begin (); some_iterator! = some_vector.end (); ++ some_iterator) {// faça coisas}

Portanto, assumindo e indexável std::vector<>como o contêiner, não há uma boa razão para preferir um ao outro, passando sequencialmente pelo contêiner. Se você precisar consultar com freqüência índices mais antigos ou mais recentes, a versão indexada é mais apropriada.

Em geral, é preferível usar os iteradores porque os algoritmos os utilizam e o comportamento pode ser controlado (e implicitamente documentado) alterando o tipo do iterador. Os locais da matriz podem ser usados ​​no lugar dos iteradores, mas a diferença sintática será destacada.

user22044
fonte
1

Não uso iteradores pelo mesmo motivo que não gosto de declarações foreach. Ao ter vários loops internos, é bastante difícil acompanhar as variáveis ​​globais / membros sem ter que lembrar todos os valores locais e nomes de iteradores também. O que acho útil é usar dois conjuntos de índices para diferentes ocasiões:

for(int i=0;i<anims.size();i++)
  for(int j=0;j<bones.size();j++)
  {
     int animIndex = i;
     int boneIndex = j;


     // in relatively short code I use indices i and j
     ... animation_matrices[i][j] ...

     // in long and complicated code I use indices animIndex and boneIndex
     ... animation_matrices[animIndex][boneIndex] ...


  }

Eu nem quero abreviar coisas como "animation_matrices [i]" para algum iterador aleatório "anim_matrix"-named-iterator, por exemplo, porque então você não pode ver claramente de qual matriz esse valor é originado.

AareP
fonte
Não vejo como os índices são melhores nesse sentido. Você poderia facilmente usar iteradores e escolher apenas uma convenção para seus nomes: it, jt, kt, etc., ou mesmo continuar usando i, j, k, etc. E se você precisa saber exatamente o que um iterador representa, em seguida, para mim algo como for (auto anim = anims.begin(); ...) for (auto anim_bone = anim->bones.begin(); ...) anim_bone->wobble()seria mais descritivo do que ter que indexar continuamente como animation_matrices[animIndex][boneIndex].
Underscore_d
uau, parece que eras atrás quando escrevi essa opinião. hoje em dia, usando os iteradores foreach e c ++ sem se preocupar muito. Eu acho que trabalhar com código de buggy durante anos se acumula a tolerância de um, por isso é mais fácil de aceitar todas as sintaxes e convenções ... desde que ele funciona, e enquanto um pode ir para casa você sabe;)
AareP
Haha, de fato, eu realmente não olhei quantos anos isso tinha antes! Outra coisa que de alguma forma não pensei na última vez foi que hoje em dia também temos o forloop baseado em intervalo , o que torna a maneira baseada em iterador de fazer isso ainda mais concisa.
underscore_d
1
  • Se você gosta de estar próximo do metal / não confia nos detalhes de sua implementação, não use iteradores.
  • Se você alternar regularmente um tipo de coleção para outro durante o desenvolvimento, use iteradores.
  • Se você achar difícil lembrar como iterar diferentes tipos de coleções (talvez você tenha vários tipos de várias fontes externas diferentes em uso), use iteradores para unificar os meios pelos quais você percorre os elementos. Isso se aplica à troca de uma lista vinculada por uma lista de matrizes.

Realmente, isso é tudo o que existe. Não é como se você fosse ganhar mais concisão de qualquer maneira, em média, e se a concisão realmente é seu objetivo, você sempre pode recorrer às macros.

Engenheiro
fonte
1

Se você tiver acesso aos recursos do C ++ 11 , também poderá usar um loop baseado em intervalofor para iterar sobre seu vetor (ou qualquer outro contêiner) da seguinte maneira:

for (auto &item : some_vector)
{
     //do stuff
}

O benefício desse loop é que você pode acessar elementos do vetor diretamente através da itemvariável, sem correr o risco de bagunçar um índice ou cometer um erro ao fazer a referência a um iterador. Além disso, o espaço reservado autoimpede que você precise repetir o tipo dos elementos do contêiner, o que o aproxima ainda mais de uma solução independente do contêiner.

Notas:

  • Se você precisa do índice do elemento em seu loop e o operator[]existe para o seu contêiner (e é rápido o suficiente para você), é melhor seguir seu primeiro caminho.
  • Um forloop baseado em intervalo não pode ser usado para adicionar / excluir elementos em / de um contêiner. Se você quiser fazer isso, é melhor seguir a solução dada por Brian Matthews.
  • Se você não deseja alterar os elementos em seu recipiente, então você deve usar a palavra-chave constda seguinte forma: for (auto const &item : some_vector) { ... }.
buzinar
fonte
0

Ainda melhor do que "dizer à CPU o que fazer" (imperativo) é "dizer às bibliotecas o que você deseja" (funcional).

Então, ao invés de usar loops, você deve aprender os algoritmos presentes no stl.

Cyber ​​Oliveira
fonte
0

Para independência do contêiner

all2one
fonte
0

Eu sempre uso o índice de matriz, porque muitas aplicações minhas exigem algo como "exibir imagem em miniatura". Então eu escrevi algo como isto:

some_vector[0].left=0;
some_vector[0].top =0;<br>

for (int i = 1; i < some_vector.size(); i++)
{

    some_vector[i].left = some_vector[i-1].width +  some_vector[i-1].left;
    if(i % 6 ==0)
    {
        some_vector[i].top = some_vector[i].top.height + some_vector[i].top;
        some_vector[i].left = 0;
    }

}
Krirk
fonte
0

Ambas as implementações estão corretas, mas eu preferiria o loop 'for'. Como decidimos usar um vetor e não qualquer outro contêiner, o uso de índices seria a melhor opção. O uso de iteradores com vetores perderia o benefício de ter os objetos em blocos de memória contínua, o que ajuda a facilitar o acesso.

messias
fonte
2
"O uso de iteradores com vetores perderia o grande benefício de ter os objetos em blocos de memória contínuos, o que ajuda a facilitar seu acesso". [citação necessária]. Por quê? Você acha que um incremento de um iterador para um contêiner contíguo não pode ser implementado como uma simples adição?
underscore_d
0

Eu senti que nenhuma das respostas aqui explica por que gosto de iteradores como um conceito geral sobre indexação em contêineres. Observe que a maior parte da minha experiência no uso de iteradores não vem do C ++, mas de linguagens de programação de nível superior, como Python.

A interface do iterador impõe menos requisitos aos consumidores da sua função, o que permite que os consumidores façam mais com ela.

Se tudo que você precisa é ser capaz de transmitir-iterate, o desenvolvedor não se limita ao uso de recipientes indexáveis - eles podem usar qualquer classe de implementação operator++(T&), operator*(T)e operator!=(const &T, const &T).

#include <iostream>
template <class InputIterator>
void printAll(InputIterator& begin, InputIterator& end)
{
    for (auto current = begin; current != end; ++current) {
        std::cout << *current << "\n";
    }
}

// elsewhere...

printAll(myVector.begin(), myVector.end());

Seu algoritmo funciona para o caso de você precisar - repetindo um vetor - mas também pode ser útil para aplicativos que você não necessariamente antecipa:

#include <random>

class RandomIterator
{
private:
    std::mt19937 random;
    std::uint_fast32_t current;
    std::uint_fast32_t floor;
    std::uint_fast32_t ceil;

public:
    RandomIterator(
        std::uint_fast32_t floor = 0,
        std::uint_fast32_t ceil = UINT_FAST32_MAX,
        std::uint_fast32_t seed = std::mt19937::default_seed
    ) :
        floor(floor),
        ceil(ceil)
    {
        random.seed(seed);
        ++(*this);
    }

    RandomIterator& operator++()
    {
        current = floor + (random() % (ceil - floor));
    }

    std::uint_fast32_t operator*() const
    {
        return current;
    }

    bool operator!=(const RandomIterator &that) const
    {
        return current != that.current;
    }
};

int main()
{
    // roll a 1d6 until we get a 6 and print the results
    RandomIterator firstRandom(1, 7, std::random_device()());
    RandomIterator secondRandom(6, 7);
    printAll(firstRandom, secondRandom);

    return 0;
}

Tentar implementar um operador de colchetes que faça algo semelhante a esse iterador seria artificial, enquanto a implementação do iterador é relativamente simples. O operador de colchetes também traz implicações sobre os recursos de sua classe - que você pode indexar para qualquer ponto arbitrário - que podem ser difíceis ou ineficientes de implementar.

Os iteradores também se prestam à decoração . As pessoas podem escrever iteradores que utilizam um iterador em seu construtor e ampliam sua funcionalidade:

template<class InputIterator, typename T>
class FilterIterator
{
private:
    InputIterator internalIterator;

public:
    FilterIterator(const InputIterator &iterator):
        internalIterator(iterator)
    {
    }

    virtual bool condition(T) = 0;

    FilterIterator<InputIterator, T>& operator++()
    {
        do {
            ++(internalIterator);
        } while (!condition(*internalIterator));

        return *this;
    }

    T operator*()
    {
        // Needed for the first result
        if (!condition(*internalIterator))
            ++(*this);
        return *internalIterator;
    }

    virtual bool operator!=(const FilterIterator& that) const
    {
        return internalIterator != that.internalIterator;
    }
};

template <class InputIterator>
class EvenIterator : public FilterIterator<InputIterator, std::uint_fast32_t>
{
public:
    EvenIterator(const InputIterator &internalIterator) :
        FilterIterator<InputIterator, std::uint_fast32_t>(internalIterator)
    {
    }

    bool condition(std::uint_fast32_t n)
    {
        return !(n % 2);
    }
};


int main()
{
    // Rolls a d20 until a 20 is rolled and discards odd rolls
    EvenIterator<RandomIterator> firstRandom(RandomIterator(1, 21, std::random_device()()));
    EvenIterator<RandomIterator> secondRandom(RandomIterator(20, 21));
    printAll(firstRandom, secondRandom);

    return 0;
}

Embora esses brinquedos possam parecer comuns, não é difícil imaginar o uso de iteradores e decoradores de iteradores para fazer coisas poderosas com uma interface simples - decorando um iterador de encaminhamento somente dos resultados do banco de dados com um iterador que constrói um objeto de modelo a partir de um único resultado, por exemplo . Esses padrões permitem a iteração com eficiência de memória de conjuntos infinitos e, com um filtro como o que escrevi acima, uma avaliação potencialmente lenta dos resultados.

Parte do poder dos modelos C ++ é a sua interface do iterador, quando aplicada a tipos de matrizes C de comprimento fixo, decai para uma aritmética simples e eficiente do ponteiro , tornando-a uma abstração verdadeiramente de custo zero.

Marcus Harrison
fonte