A mesma consideração se aplica aos argumentos da função.
Maxim Egorushkin
3
Na verdade, isso tem pouco a ver com o intervalo para. O mesmo pode ser dito de qualquer um auto (const)(&) x = <expr>;.
Matthieu M.
2
@ MatthieuM: Isso tem muito a ver com o intervalo, é claro! Considere um iniciante que vê várias sintaxes e não pode escolher qual formulário usar. O objetivo das "perguntas e respostas" era tentar esclarecer e explicar as diferenças de alguns casos (e discutir casos que compilam muito bem, mas são meio ineficientes devido a cópias profundas inúteis etc.).
Mr.C64
2
@ Mr.C64: No que me diz respeito, isso tem mais a ver auto, em geral, do que com base no intervalo; você pode usar perfeitamente o intervalo com base, sem nenhum auto! for (int i: v) {}está perfeitamente bem. Obviamente, a maioria dos pontos que você levanta em sua resposta pode ter mais a ver com o tipo do que com auto... mas, a partir da pergunta, não está claro onde está o ponto de dor. Pessoalmente, competiria por me afastar autoda questão; ou talvez explique que, se você usa autoou nomeia explicitamente o tipo, a questão é focada em valor / referência.
precisa
1
@ MatthieuM .: Estou aberto a alterar o título ou editar a pergunta de alguma forma que possa torná-la mais clara ... Novamente, meu foco foi discutir várias opções de sintaxe baseada em intervalo (mostrando o código que compila, mas é ineficiente, código que falha na compilação etc.) e tentando oferecer alguma orientação a alguém (especialmente no nível iniciante) que se aproxima do C ++ 11 baseado em intervalo para loops.
Mr.C64
Respostas:
389
Vamos começar a diferenciar entre observar os elementos no contêiner e modificá- los no lugar.
Observando os elementos
Vamos considerar um exemplo simples:
vector<int> v ={1,3,5,7,9};for(auto x : v)
cout << x <<' ';
O código acima imprime os elementos intno vector:
13579
Agora considere outro caso, no qual os elementos vetoriais não são apenas números inteiros simples, mas instâncias de uma classe mais complexa, com construtor de cópia personalizado, etc.
// A sample test class, with custom copy semantics.class X
{public:
X(): m_data(0){}
X(int data): m_data(data){}~X(){}
X(const X& other): m_data(other.m_data){ cout <<"X copy ctor.\n";}
X&operator=(const X& other){
m_data = other.m_data;
cout <<"X copy assign.\n";return*this;}intGet()const{return m_data;}private:int m_data;};
ostream&operator<<(ostream& os,const X& x){
os << x.Get();return os;}
Se usarmos a for (auto x : v) {...}sintaxe acima com esta nova classe:
vector<X> v ={1,3,5,7,9};
cout <<"\nElements:\n";for(auto x : v){
cout << x <<' ';}
a saída é algo como:
[... copy constructor calls forvector<X> initialization ...]Elements:
X copy ctor.1 X copy ctor.3 X copy ctor.5 X copy ctor.7 X copy ctor.9
Como pode ser lido a partir da saída, as chamadas do construtor de cópia são feitas durante as iterações de loop baseadas em intervalo.
Isso ocorre porque estamos capturando os elementos do contêiner por valor
(a auto xparte emfor (auto x : v) ).
Este é um código ineficiente , por exemplo, se esses elementos são instâncias std::string, alocações de memória heap podem ser feitas, com viagens caras ao gerenciador de memória, etc. Isso é inútil se queremos apenas observar os elementos em um contêiner.
Portanto, uma sintaxe melhor está disponível: captura por constreferência , ou seja const auto&:
vector<X> v ={1,3,5,7,9};
cout <<"\nElements:\n";for(constauto& x : v){
cout << x <<' ';}
Sem nenhuma chamada de construtor de cópia espúria (e potencialmente cara).
Assim, quando observando elementos em um recipiente (ou seja, para acesso somente leitura), a seguinte sintaxe é bom para simples barato-a-cópia tipos, como int, double, etc .:
for(auto elem : container)
Caso contrário , a captura por constreferência é melhor no caso geral , para evitar chamadas de construtor de cópia inúteis (e potencialmente caras):
for(constauto& elem : container)
Modificando os elementos no contêiner
Se quisermos modificar os elementos em um contêiner usando o intervalo for, os itens acima for (auto elem : container)efor (const auto& elem : container)
sintaxes as incorretas.
De fato, no primeiro caso, elemarmazena uma cópia do elemento original, para que as modificações feitas sejam perdidas e não armazenadas persistentemente no contêiner, por exemplo:
vector<int> v ={1,3,5,7,9};for(auto x : v)// <-- capture by value (copy)
x *=10;// <-- a local temporary copy ("x") is modified,// *not* the original vector element.for(auto x : v)
cout << x <<' ';
A saída é apenas a sequência inicial:
13579
Em vez disso, uma tentativa de usar for (const auto& x : v) apenas falha na compilação.
O g ++ gera uma mensagem de erro mais ou menos assim:
TestRangeFor.cpp:138:11: error: assignment of read-only reference 'x'
x *=10;^
A abordagem correta neste caso é capturar por não constreferência:
vector<int> v ={1,3,5,7,9};for(auto& x : v)
x *=10;for(auto x : v)
cout << x <<' ';
A saída é (como esperado):
1030507090
Essa for (auto& elem : container)sintaxe também funciona para tipos mais complexos, por exemplo, considerando um vector<string>:
vector<string> v ={"Bob","Jeff","Connie"};// Modify elements in place: use "auto &"for(auto& x : v)
x ="Hi "+ x +"!";// Output elements (*observing* --> use "const auto&")for(constauto& x : v)
cout << x <<' ';
a saída é:
HiBob!HiJeff!HiConnie!
O caso especial de iteradores de proxy
Suponha que temos vector<bool>ae queremos inverter o estado booleano lógico de seus elementos, usando a sintaxe acima:
vector<bool> v ={true,false,false,true};for(auto& x : v)
x =!x;
O código acima falha ao compilar.
O g ++ gera uma mensagem de erro semelhante a esta:
TestRangeFor.cpp:168:20: error: invalid initialization of non-const reference of
type 'std::_Bit_reference&' from an rvalue of type 'std::_Bit_iterator::referen
ce {aka std::_Bit_reference}'for(auto& x : v)^
O problema é que o std::vectormodelo é especializada para bool, com uma implementação que pacotes os bools ao espaço optimize (cada valor booleano é armazenado em um pouco, oito bits de "booleanas" em um byte).
Por esse motivo (como não é possível retornar uma referência a um único bit),
vector<bool>utiliza o chamado padrão "proxy iterator" . Um "iteração proxy" é uma iteração que, quando desreferenciado, se não se obter um comum bool &, mas em vez disso os retornos (em valor) um objeto temporária , que é uma classe de proxy conversível abool . (Veja também esta pergunta e respostas relacionadas aqui no StackOverflow.)
Para modificar no lugar os elementos de vector<bool>, um novo tipo de sintaxe (usando auto&&) deve ser usado:
for(auto&& x : v)
x =!x;
O código a seguir funciona bem:
vector<bool> v ={true,false,false,true};// Invert boolean statusfor(auto&& x : v)// <-- note use of "auto&&" for proxy iterators
x =!x;// Print new element values
cout << boolalpha;for(constauto& x : v)
cout << x <<' ';
e saídas:
falsetruetruefalse
Observe que a for (auto&& elem : container)sintaxe também funciona nos outros casos de iteradores comuns (sem proxy) (por exemplo, para um vector<int>ou umvector<string> ).
(Como uma observação lateral, a sintaxe de "observação" acima mencionada for (const auto& elem : container)também funciona bem para o caso do iterador de proxy.)
Resumo
A discussão acima pode ser resumida nas seguintes diretrizes:
Para observar os elementos, use a seguinte sintaxe:
for(constauto& elem : container)// capture by const reference
Se os objetos são baratos para copiar (como ints, doubles, etc.), é possível usar um formulário um pouco simplificado:
for(auto elem : container)// capture by value
Para modificar os elementos no lugar, use:
for(auto& elem : container)// capture by (non-const) reference
Se o contêiner usar "iteradores de proxy" (como std::vector<bool>), use:
for(auto&& elem : container)// capture by &&
Obviamente, se houver uma necessidade de fazer uma cópia local do elemento dentro do corpo do loop, capturar por value ( for (auto elem : container)) é uma boa opção.
Notas adicionais sobre código genérico
No código genérico , como não podemos fazer suposições sobre o tipo genérico Tser barato de copiar, no modo de observação é seguro usá-lo sempre for (const auto& elem : container).
(Isso não acionará cópias inúteis potencialmente caras, funcionará bem também para tipos baratos de copiar int, como também para contêineres usando iteradores de proxy, como std::vector<bool>.)
Além disso, no modo de modificação , se queremos que o código genérico funcione também no caso de iteradores de proxy, a melhor opção é for (auto&& elem : container).
(Isso funcionará bem também para contêineres que usam iteradores não proxy comuns, como std::vector<int>ou std::vector<string>.)
Portanto, no código genérico , as seguintes diretrizes podem ser fornecidas:
Por que nem sempre usar auto&&? Existe um const auto&&?
Martin Ba
1
Eu acho que você está perdendo o caso em que você realmente precisa de uma cópia dentro do loop?
precisa saber é o seguinte
6
"Se o contêiner usa" iteradores de proxy "" - e você sabe que usa "iteradores de proxy" (que pode não ser o caso no código genérico). Então, acho que o melhor é de fato auto&&, uma vez que cobre auto&igualmente bem.
Christian Rau
5
Obrigado, essa foi realmente uma ótima "introdução ao curso intensivo" à sintaxe de e algumas dicas para o intervalo, para um programador de C #. +1.
precisa saber é o seguinte
17
Não há maneira correta de usar for (auto elem : container), ou for (auto& elem : container)ou for (const auto& elem : container). Você acabou de expressar o que deseja.
Deixe-me elaborar sobre isso. Vamos dar um passeio.
for(auto elem : container)...
Este é o açúcar sintático para:
for(auto it = container.begin(); it != container.end();++it){// Observe that this is a copy by value.auto elem =*it;}
Você pode usar este se o seu contêiner contiver elementos que são baratos para copiar.
for(auto& elem : container)...
Este é o açúcar sintático para:
for(auto it = container.begin(); it != container.end();++it){// Now you're directly modifying the elements// because elem is an lvalue referenceauto& elem =*it;}
Use isso quando desejar gravar diretamente nos elementos no contêiner, por exemplo.
for(constauto& elem : container)...
Este é o açúcar sintático para:
for(auto it = container.begin(); it != container.end();++it){// You just want to read stuff, no modificationconstauto& elem =*it;}
Como o comentário diz, apenas para leitura. E é isso, tudo está "correto" quando usado corretamente.
Eu pretendia dar algumas orientações, com exemplos de códigos compilando (mas sendo ineficientes) ou falhando em compilar, explicando o motivo e tentando propor algumas soluções.
Mr.C64
2
@ Mr.C64 Oh, desculpe - acabei de perceber que essa é uma daquelas perguntas do tipo FAQ. Eu sou novo neste site. Desculpas! Sua resposta é ótima, eu a votei - mas também queria fornecer uma versão mais concisa para aqueles que querem o essencial . Felizmente, não estou me intrometendo.
1
@ Mr.C64, qual é o problema com o OP respondendo à pergunta também? É apenas mais uma resposta válida.
precisa saber é o seguinte
1
@mfontanini: Não há absolutamente nenhum problema se alguém postar alguma resposta, ainda melhor que a minha. O objetivo final é dar uma contribuição de qualidade à comunidade (especialmente para iniciantes que podem se sentir meio perdidos diante de diferentes sintaxes e opções diferentes que o C ++ oferece).
Mas e se o contêiner retornar apenas referências modificáveis e eu quiser deixar claro que não desejo modificá-las no loop? Eu não deveria então usar auto const ¶ esclarecer minha intenção?
RedX
@ RedX: O que é uma "referência modificável"?
Lightness Races in Orbit
2
@ RedX: As referências nunca são conste nunca são mutáveis. Enfim, minha resposta para você é sim, eu faria .
Lightness Races in Orbit
4
Embora isso possa funcionar, acho que este é um péssimo conselho em comparação com a abordagem mais sutil e considerada dada pela excelente e abrangente resposta do Sr. C64 dada acima. Reduzir para o denominador menos comum não é para isso que serve o C ++.
Embora a motivação inicial do loop range-for possa ter sido fácil de iterar sobre os elementos de um contêiner, a sintaxe é genérica o suficiente para ser útil mesmo para objetos que não são puramente contêineres.
O requisito sintático para o loop for é esse range_expressionsuporte begin()e end()como funções - como funções de membro do tipo que ele avalia ou como funções de não-membro que levam uma instância do tipo.
Como um exemplo artificial, pode-se gerar um intervalo de números e iterar no intervalo usando a seguinte classe.
auto (const)(&) x = <expr>;
.auto
, em geral, do que com base no intervalo; você pode usar perfeitamente o intervalo com base, sem nenhumauto
!for (int i: v) {}
está perfeitamente bem. Obviamente, a maioria dos pontos que você levanta em sua resposta pode ter mais a ver com o tipo do que comauto
... mas, a partir da pergunta, não está claro onde está o ponto de dor. Pessoalmente, competiria por me afastarauto
da questão; ou talvez explique que, se você usaauto
ou nomeia explicitamente o tipo, a questão é focada em valor / referência.Respostas:
Vamos começar a diferenciar entre observar os elementos no contêiner e modificá- los no lugar.
Observando os elementos
Vamos considerar um exemplo simples:
O código acima imprime os elementos
int
novector
:Agora considere outro caso, no qual os elementos vetoriais não são apenas números inteiros simples, mas instâncias de uma classe mais complexa, com construtor de cópia personalizado, etc.
Se usarmos a
for (auto x : v) {...}
sintaxe acima com esta nova classe:a saída é algo como:
Como pode ser lido a partir da saída, as chamadas do construtor de cópia são feitas durante as iterações de loop baseadas em intervalo.
Isso ocorre porque estamos capturando os elementos do contêiner por valor (a
auto x
parte emfor (auto x : v)
).Este é um código ineficiente , por exemplo, se esses elementos são instâncias
std::string
, alocações de memória heap podem ser feitas, com viagens caras ao gerenciador de memória, etc. Isso é inútil se queremos apenas observar os elementos em um contêiner.Portanto, uma sintaxe melhor está disponível: captura por
const
referência , ou sejaconst auto&
:Agora a saída é:
Sem nenhuma chamada de construtor de cópia espúria (e potencialmente cara).
Assim, quando observando elementos em um recipiente (ou seja, para acesso somente leitura), a seguinte sintaxe é bom para simples barato-a-cópia tipos, como
int
,double
, etc .:Caso contrário , a captura por
const
referência é melhor no caso geral , para evitar chamadas de construtor de cópia inúteis (e potencialmente caras):Modificando os elementos no contêiner
Se quisermos modificar os elementos em um contêiner usando o intervalo
for
, os itens acimafor (auto elem : container)
efor (const auto& elem : container)
sintaxes as incorretas.De fato, no primeiro caso,
elem
armazena uma cópia do elemento original, para que as modificações feitas sejam perdidas e não armazenadas persistentemente no contêiner, por exemplo:A saída é apenas a sequência inicial:
Em vez disso, uma tentativa de usar
for (const auto& x : v)
apenas falha na compilação.O g ++ gera uma mensagem de erro mais ou menos assim:
A abordagem correta neste caso é capturar por não
const
referência:A saída é (como esperado):
Essa
for (auto& elem : container)
sintaxe também funciona para tipos mais complexos, por exemplo, considerando umvector<string>
:a saída é:
O caso especial de iteradores de proxy
Suponha que temos
vector<bool>
ae queremos inverter o estado booleano lógico de seus elementos, usando a sintaxe acima:O código acima falha ao compilar.
O g ++ gera uma mensagem de erro semelhante a esta:
O problema é que o
std::vector
modelo é especializada parabool
, com uma implementação que pacotes osbool
s ao espaço optimize (cada valor booleano é armazenado em um pouco, oito bits de "booleanas" em um byte).Por esse motivo (como não é possível retornar uma referência a um único bit),
vector<bool>
utiliza o chamado padrão "proxy iterator" . Um "iteração proxy" é uma iteração que, quando desreferenciado, se não se obter um comumbool &
, mas em vez disso os retornos (em valor) um objeto temporária , que é uma classe de proxy conversível abool
. (Veja também esta pergunta e respostas relacionadas aqui no StackOverflow.)Para modificar no lugar os elementos de
vector<bool>
, um novo tipo de sintaxe (usandoauto&&
) deve ser usado:O código a seguir funciona bem:
e saídas:
Observe que a
for (auto&& elem : container)
sintaxe também funciona nos outros casos de iteradores comuns (sem proxy) (por exemplo, para umvector<int>
ou umvector<string>
).(Como uma observação lateral, a sintaxe de "observação" acima mencionada
for (const auto& elem : container)
também funciona bem para o caso do iterador de proxy.)Resumo
A discussão acima pode ser resumida nas seguintes diretrizes:
Para observar os elementos, use a seguinte sintaxe:
Se os objetos são baratos para copiar (como
int
s,double
s, etc.), é possível usar um formulário um pouco simplificado:Para modificar os elementos no lugar, use:
Se o contêiner usar "iteradores de proxy" (como
std::vector<bool>
), use:Obviamente, se houver uma necessidade de fazer uma cópia local do elemento dentro do corpo do loop, capturar por value (
for (auto elem : container)
) é uma boa opção.Notas adicionais sobre código genérico
No código genérico , como não podemos fazer suposições sobre o tipo genérico
T
ser barato de copiar, no modo de observação é seguro usá-lo semprefor (const auto& elem : container)
.(Isso não acionará cópias inúteis potencialmente caras, funcionará bem também para tipos baratos de copiar
int
, como também para contêineres usando iteradores de proxy, comostd::vector<bool>
.)Além disso, no modo de modificação , se queremos que o código genérico funcione também no caso de iteradores de proxy, a melhor opção é
for (auto&& elem : container)
.(Isso funcionará bem também para contêineres que usam iteradores não proxy comuns, como
std::vector<int>
oustd::vector<string>
.)Portanto, no código genérico , as seguintes diretrizes podem ser fornecidas:
Para observar os elementos, use:
Para modificar os elementos no lugar, use:
fonte
auto&&
? Existe umconst auto&&
?auto&&
, uma vez que cobreauto&
igualmente bem.Não há maneira correta de usar
for (auto elem : container)
, oufor (auto& elem : container)
oufor (const auto& elem : container)
. Você acabou de expressar o que deseja.Deixe-me elaborar sobre isso. Vamos dar um passeio.
Este é o açúcar sintático para:
Você pode usar este se o seu contêiner contiver elementos que são baratos para copiar.
Este é o açúcar sintático para:
Use isso quando desejar gravar diretamente nos elementos no contêiner, por exemplo.
Este é o açúcar sintático para:
Como o comentário diz, apenas para leitura. E é isso, tudo está "correto" quando usado corretamente.
fonte
O meio correto é sempre
Isso garantirá a preservação de toda a semântica.
fonte
auto const &
para esclarecer minha intenção?const
e nunca são mutáveis. Enfim, minha resposta para você é sim, eu faria .Embora a motivação inicial do loop range-for possa ter sido fácil de iterar sobre os elementos de um contêiner, a sintaxe é genérica o suficiente para ser útil mesmo para objetos que não são puramente contêineres.
O requisito sintático para o loop for é esse
range_expression
suportebegin()
eend()
como funções - como funções de membro do tipo que ele avalia ou como funções de não-membro que levam uma instância do tipo.Como um exemplo artificial, pode-se gerar um intervalo de números e iterar no intervalo usando a seguinte classe.
Com a seguinte
main
função,seria possível obter a seguinte saída.
fonte