Quanto é demais com a palavra-chave automática C ++ 11?

218

Eu tenho usado a nova autopalavra-chave disponível no padrão C ++ 11 para tipos de modelos complicados, e é para isso que acredito que foi projetada. Mas também estou usando para coisas como:

auto foo = std::make_shared<Foo>();

E mais ceticamente para:

auto foo = bla(); // where bla() return a shared_ptr<Foo>

Não vi muita discussão sobre esse tópico. Parece que autopoderia ser usado em excesso, uma vez que um tipo geralmente é uma forma de documentação e verificações de sanidade. Onde você desenha a linha de uso autoe quais são os casos de uso recomendados para esse novo recurso?

Para esclarecer: não estou pedindo uma opinião filosófica; Estou solicitando o uso pretendido dessa palavra-chave pelo comitê padrão, possivelmente com comentários sobre como esse uso pretendido é realizado na prática.

Nota lateral: Esta pergunta foi movida para SE.Programmers e depois de volta para Stack Overflow. Discussões sobre isso podem ser encontradas nesta meta questão .

Alan Turing
fonte
Este é um site de perguntas e respostas, não um site de discussão. Você fez uma pergunta muito geral e eu duvido que alguém possa lhe dar outra coisa que não seja altamente subjetiva. (é por isso que -1)
TravisG
15
@ heishe, eu adicionei um esclarecimento. Se você lê a pergunta de maneira muito geral, parece estar pedindo uma opinião subjetiva, mas, na verdade, se você usou a autopalavra - chave, sabe como ela deve ser usada. É isso que estou perguntando, como alguém que é novo nesse recurso, é como devo usá-lo?
Alan Turing
13
Eu já vi essa discussão em todo o lugar quando o C # foi introduzido var(ou seja, depois que as pessoas pensavam que não era uma digitação dinâmica, afinal). Se você quiser, pode começar com esta pergunta e fazer as perguntas relacionadas.
R. Martinho Fernandes
2
@ Lex: Ou algo é legal ou não é; chamar algo de "ruim" legal é subjetivo por definição. Ou seja, chamar auto foo = bla();"ruim" é claramente uma opinião, não um fato, que faz essa pergunta e responde a uma discussão, o que a torna relevante para os Programadores SE, que é exatamente o que indicam os votos próximos. / shrug
ildjarn
3
Visão de Herb Sutter sobre este assunto: herbsutter.com/2013/06/13/…
rafak

Respostas:

131

Acho que se deve usar a autopalavra-chave sempre que for difícil dizer como escrever o tipo à primeira vista, mas o tipo do lado direito de uma expressão é óbvio. Por exemplo, usando:

my_multi_type::nth_index<2>::type::key_type::composite_key_type::
    key_extractor_tuple::tail_type::head_type::result_type

para obter o tipo de chave composta boost::multi_index, mesmo sabendo que é int. Você não pode simplesmente escrever intporque pode ser alterado no futuro. Eu escreveria autoneste caso.

Portanto, se a autopalavra - chave melhorar a legibilidade em um caso específico, use-a. Você pode escrever autoquando é óbvio para o leitor que tipo autorepresenta.

aqui estão alguns exemplos:

auto foo = std::make_shared<Foo>();   // obvious
auto foo = bla();                     // unclear. don't know which type `foo` has

const size_t max_size = 100;
for ( auto x = max_size; x > 0; --x ) // unclear. could lead to the errors
                                      // since max_size is unsigned

std::vector<some_class> v;
for ( auto it = v.begin(); it != v.end(); ++it )
                                      // ok, since I know that `it` has an iterator type
                                      // (don't really care which one in this context)
Kirill V. Lyadvinsky
fonte
18
Isso não parece uma recomendação muito boa. Quantas vezes você não conhece o tipo do objeto? (Fora dos modelos, é isso.) E se você não souber que eles digitam, procure, não seja preguiçoso e use auto.
Paul Manta
9
@Paul: muitas vezes você sabe apenas ou precisa conhecer a parte mais importante do tipo, por exemplo, que é um iterador, mas você não sabe e não se importa se é um iterador de vetores ou iterador de links Lista; nesses casos, você realmente não quer gastar tempo e espaço na tela tentando descobrir como escrever os tipos.
Lie Ryan
45
Quer o C ++ diehards goste ou não, o C ++ 0x atrairá pessoas que nunca teriam usado o C ++. E aqueles usarão o automóvel em todo o lugar.
Prof. Falken
6
@ R.MartinhoFernandes - não, a única coisa que fica clara é que tudo o que bla()você devolve foo.
Luis Machuca
8
@LuisMachuca, minha resposta foi uma maneira explícita de dizer que é desonesto dar um exemplo didático de nomes ruins de variáveis ​​e funções e culpar sua falta de legibilidade por inferência de tipo.
R. Martinho Fernandes
62

Use em autotodos os lugares que puder - particularmente const autopara que os efeitos colaterais sejam menos preocupantes. Você não precisará se preocupar com os tipos, exceto nos casos óbvios, mas eles ainda serão verificados estaticamente para você e você poderá evitar algumas repetições. Onde autonão for possível, você pode usar decltypepara expressar tipos semanticamente como contratos com base em expressões. Seu código será diferente, mas será uma mudança positiva.

Jon Purdy
fonte
12
Eu diria particularmente "const auto"
Viktor Sehr -
2
Melhor uso auto&&em situações complexas edmundv.home.xs4all.nl/blog/2014/01/28/…
KindDragon 6/14
2
@KindDragon: Essa é uma boa regra geral, mas eu prefiro usar const auto&ou a const automenos que eu queira mudar ou mudar explicitamente.
Jon Purdy
" Use auto em todos os lugares que puder ". Isso significa que eu deveria escrever em auto str = std::string();vez de std::string str;?
Calmarius 23/03/19
4
O uso automático de todo o local tornará seu código menos legível e mais difícil de depurar, pois você precisará deduzir os tipos enquanto lê o código.
Sub
52

Fácil. Use-o quando não se importar com o tipo. Por exemplo

for (const auto & i : some_container) {
   ...

Tudo o que me importa aqui é o que iestá no recipiente.

É um pouco como typedefs.

typedef float Height;
typedef double Weight;
//....
Height h;
Weight w;

Aqui, eu não me importo se he wsão carros alegóricos ou duplos, apenas que eles sejam do tipo adequado para expressar alturas e pesos .

Ou considere

for (auto i = some_container .begin (); ...

Aqui tudo o que me interessa é que é um iterador adequado, suportando operator++(), é como digitar pato a esse respeito.

Além disso, o tipo de lambdas não pode ser escrito, assim auto f = []...como o bom estilo. A alternativa é converter para, std::functionmas isso vem com sobrecarga.

Eu realmente não posso conceber um "abuso" de auto. O mais próximo que posso imaginar é privar-se de uma conversão explícita para algum tipo significativo - mas você não usaria autopara isso, construiria um objeto do tipo desejado.

Se você pode remover alguma redundância no seu código sem introduzir efeitos colaterais, deve ser bom fazê-lo.

Contra-exemplos (emprestados das respostas de outra pessoa):

auto i = SomeClass();
for (auto x = make_unsigned (y); ...)

Aqui nos importamos com o tipo, então devemos escrever Someclass i;efor(unsigned x = y;...

spraff
fonte
1
Hum. Não tão fácil. Ele é compilado e executado, e você acaba de dar um tiro no pé se os itens são objetos não triviais - sua iteração chama o construtor e o destruidor de cópia a cada etapa da iteração. Se você usar cegamente auto em um iterador baseado em intervalo, provavelmente deve ser "for (const auto & item: some_container)" em vez de "for (auto item: some_container)".
Don escotilha
2
Nem sempre, mas ok, você provavelmente quer referências. E daí? Isso não tem nada a ver auto.
spraff
Eu realmente não entendo o seu último comentário. Tentei explicar por que sua estratégia não parece muito boa para mim e por que estou votando contra.
Don escotilha
5
Estou dizendo que se você usa referências ou não é ortogonal se você usa automático ou não. Vou editá-lo para adicionar a referência, porque é certo que geralmente é o que se quer fazer, mas é totalmente irrelevante para o tópico em questão.
spraff
43

Vá em frente. Use em autoqualquer lugar que facilite a escrita de código.

Todo novo recurso em qualquer idioma será usado em excesso por pelo menos alguns tipos de programadores. Somente através do uso moderado de alguns programadores experientes (e não noobs) é que os demais programadores experientes aprendem os limites do uso adequado. O uso excessivo extremo geralmente é ruim, mas pode ser bom porque esse uso excessivo pode levar a melhorias no recurso ou a um recurso melhor para substituí-lo.

Mas se eu estivesse trabalhando em código com mais de algumas linhas como

auto foo = bla();

onde o tipo é indicado zero vezes, convém alterar essas linhas para incluir tipos. O primeiro exemplo é ótimo, já que o tipo é declarado uma vez e autoevita a necessidade de escrever tipos de modelo confusos duas vezes. Viva o C ++++. Mas mostrar explicitamente o tipo zero vezes, se não for facilmente visível em uma linha próxima, me deixa nervoso, pelo menos em C ++ e seus sucessores imediatos. Para outras linguagens projetadas para trabalhar em um nível mais alto com mais abstração, polimorfismo e genicidade, tudo bem.

DarenW
fonte
38

No C ++ e Beyond 2012, no painel Ask Us Anything , houve uma troca fantástica entre Andrei Alexandrescu, Scott Meyers e Herb Sutter falando sobre quando usar e não usarauto . Pule para o minuto 25:03 para uma discussão de 4 minutos. Todos os três alto-falantes dão excelentes pontos que devem ser lembrados para quando não usar auto.

Encorajo muito as pessoas a chegarem a sua própria conclusão, mas minha opção foi usar em autoqualquer lugar, a menos que :

  1. Dói legibilidade
  2. Existe uma preocupação com a conversão automática de tipos (por exemplo, de construtores, atribuição, tipos intermediários de modelos, conversão implícita entre larguras inteiras)

O uso liberal de explicitajuda a reduzir a preocupação com o último, o que ajuda a minimizar a quantidade de tempo que o primeiro é um problema.

Reformulando o que Herb disse, "se você não estiver usando X, Y e Z, use auto. Aprenda o que X, Y e Z são e vá adiante e use em autoqualquer outro lugar".

Sean
fonte
4
Provavelmente, também vale a liga "quase sempre auto" - herbsutter.com/2013/08/12/...
Rob Starling
37

Sim, pode ser usado em excesso em detrimento da legibilidade. Sugiro usá-lo nos contextos em que tipos exatos são longos, indizíveis ou pouco importantes para facilitar a leitura, e as variáveis ​​têm vida curta. Por exemplo, o tipo de iterador geralmente é longo e não é importante; portanto auto, funcionaria:

   for(auto i = container.begin(); i != container.end(); ++i);

auto aqui não prejudica a legibilidade.

Outro exemplo é o tipo de regra do analisador, que pode ser longo e complicado. Comparar:

   auto spaces = space & space & space;

com

r_and_t<r_and_t<r_char_t<char>&, r_char_t<char>&>, r_char_t<char>&> spaces = 
   space & space & space;

Por outro lado, quando o tipo é conhecido e é simples, é muito melhor se declarar explicitamente:

int i = foo();

ao invés de

auto i = foo();
Gene Bushuyev
fonte
2
Obviamente, ter um loop for baseado em intervalo no idioma torna seu primeiro exemplo menos emocionante. 8v)
Fred Larson,
@Fred: o tipo pode ainda ser complicado (eu estou pensando recipientes associativas)
Matthieu M.
3
@Fred: Sempre que seus limites não forem begin()e end(), ou o tamanho da sua etapa for diferente de um, ou você estiver modificando o contêiner enquanto faz um loop, a declaração de base para o intervalo não o ajudará.
Dennis Zickefoose
6
@geotavros: E r_and_t<r_and_t<r_char_t<char>&, r_char_t<char>&>, r_char_t<char>&>faz?
Dennis Zickefoose
7
@geotavros: Ou você pode ver qual spaceé o tipo e procurar por isso. De qualquer forma, essas são as informações mais úteis ... afinal, o problema não é "que tipo é essa nova variável", mas "o que space & space & spacesignifica?" O tipo real da expressão é apenas ruído.
Dennis Zickefoose
19

auto pode ser muito perigoso em combinação com modelos de expressão que são muito usados ​​por bibliotecas de álgebra linear, como Eigen ou OpenCV.

auto A = Matrix(...);
auto B = Matrix(...);
auto C = A * B; // C is not a matrix. It is a matrix EXPRESSION.
cout << C; // The expression is evaluated and gives the expected result.
... // <code modifying A or B>
cout << C; // The expression is evaluated AGAIN and gives a DIFFERENT result.

Erros causados ​​por esse tipo de erro são um grande problema para depuração. Um remédio possível é converter explicitamente o resultado para o tipo esperado, se você estiver empenhado em usar auto para o estilo de declaração da esquerda para a direita.

auto C = Matrix(A * B); // The expression is now evaluated immediately.
morotspaj
fonte
1
Parece um comportamento estranho, para começar. Se eu estiver multiplicando duas matrizes, mesmo que a operação seja preguiçosa, não esperaria que fosse reavaliada, esperaria que ela mantivesse seu estado avaliado após a avaliação inicial. Se você deseja alterar os parâmetros sem modificar os parâmetros originais, não teria que reconstruir a expressão? Ou foi desenvolvido para um tipo de situação de processamento de streaming, em que os parâmetros originais mudam constantemente, mas o procedimento permanece o mesmo?
JAB
1
Quer a A*Bexpressão seja copiada ou não em uma autovariável ou em outra coisa, o comportamento que você descreve ainda está presente.
Xtofl
Nenhum bug é causado pelo uso auto.
Jim Balter
@JAB: Ele foi projetado para suportar simplificações e sinergias entre operações, por exemplo diag(A * B), não precisa desperdiçar ciclos computando os elementos fora da diagonal.
Ben Voigt
Também vi códigos como 'for (auto: vecContainer)', onde 'o' era um objeto pesado que é copiado sempre. Minha recomendação seria usá-lo para o que foi originalmente planejado, ou seja, modelos (com diretrizes de dedução difíceis) e trabalhos pesados ​​de typedef. Mesmo assim, você deve ter cuidado e distinção entre auto e auto &.
gast128 19/01
10

Uso autosem restrição e não enfrentei nenhum problema. Às vezes, até acabo usando-o para tipos simples como int. Isso faz do c ++ uma linguagem de nível superior para mim e permite declarar variáveis ​​em c ++ como em python. Depois de escrever o código python, às vezes até escrevo

auto i = MyClass();

ao invés de

MyClass i;

Este é um caso em que eu diria que é um abuso da autopalavra - chave.

Frequentemente, eu não me importo com o tipo exato de objeto, estou mais interessado em sua funcionalidade e, como os nomes das funções geralmente dizem algo sobre os objetos que eles retornam, autonão dói: por exemplo auto s = mycollection.size(), posso supor que sserá um tipo de número inteiro e, no raro caso em que me importo com o tipo exato, vamos verificar o protótipo da função (quero dizer, prefiro verificar quando preciso das informações, em vez de a priori quando o código é escrito, apenas caso isso seja útil algum dia, como em int_type s = mycollection.size()).

Sobre este exemplo da resposta aceita:

for ( auto x = max_size; x > 0; --x )

No meu código, ainda uso autoneste caso e, se quero xcancelar a assinatura, uso uma função de utilitário, chamada say make_unsigned, que expressa claramente minhas preocupações:

for ( auto x = make_unsigned(max_size); x > 0; --x )

isenção de responsabilidade: acabei de descrever meu uso, não sou competente para dar conselhos!

rafak
fonte
1
@ ChristianRau: não era sarcástico. Observe que eu não recomendo o uso auto i = MyClass().
Rafak
3
Acompanhamento: ver: herbsutter.com/2013/06/13/… . O uso de, por exemplo, as_unsignedé recomendado lá, ou mesmo auto w = widget{};.
Rafak
3

Um dos principais problemas do programa C ++ é que ele permite que você use a variável não inicializada . Isso nos leva a um comportamento desagradável e não determinístico do programa. Deve-se notar que o compilador moderno agora lança mensagens de aviso / mensagem apropriadas se o programa cansar de usá-lo.

Apenas para ilustrar isso, considere abaixo o programa c ++:

int main() {
    int x;
    int y = 0;
    y += x;
}

Se eu compilar este programa usando o compilador moderno (GCC), ele fornecerá o aviso. Esse aviso pode não ser muito óbvio se estivermos trabalhando com o código de produção complexo real.

main.cpp: Na função 'int main ()':

main.cpp: 4: 8: aviso : 'x' é usado não inicializado nesta função [-Wuninitialized]

y + = x;

    ^

==================================================== =============================== Agora, se mudarmos o nosso programa que usa auto , em seguida, compilar obtemos a seguinte:

int main() {
    auto x;
    auto y = 0;
    y += x;
}

main.cpp: Na função 'int main ()':

main.cpp: 2: 10: erro : a declaração de 'auto x' não tem inicializador

 auto x;

      ^

Com auto, não é possível usar a variável não inicializada. Essa é a grande vantagem que podemos obter (de graça), se começarmos a usar o auto .

Este conceito e outro ótimo conceito moderno de C ++ são explicados pelo especialista em C ++ Herb Shutter em sua palestra em CppCon14 :

De volta ao básico! Fundamentos do estilo C ++ moderno

Mantosh Kumar
fonte
2
Sim. E para especificar o tipo você pode inicializar como 0i, 0u, 0l, 0ul, 0.0f, 0,0, ou mesmo int (), não assinado (), double (), etc.
Robinson
6
Este argumento é ridículo. Você está dizendo "não pode esquecer o inicializador porque receberá um erro do compilador", mas isso exige que você lembre-se de usá-lo auto.
Lightness Races em órbita
1
@LightnessRacesinOrbit: Eu percebi (aprendi) esse conceito com a palestra de Herb Sutter. Achei conselhos lógicos / práticos da palestra sobre ervas, portanto pensei em compartilhar com a comunidade.
Mantosh Kumar
2
Eu não acho que isso seja uma boa motivação para usar o automóvel. Basta ativar o sinalizador de aviso do seu compilador para usar variáveis ​​não inicializadas e resolver todos os avisos. Isso não quer dizer que você não deva usar auto- mas não precisa disso para evitar esse problema.
Einpoklum 27/03
2

Um perigo que notei é em termos de referências. por exemplo

MyBigObject& ref_to_big_object= big_object;
auto another_ref = ref_to_big_object; // ?

O problema é another_ref não é realmente uma referência neste caso, é MyBigObject em vez de MyBigObject &. Você acaba copiando um objeto grande sem perceber.

Se você está obtendo uma referência diretamente de um método, pode não pensar no que realmente é.

auto another_ref = function_returning_ref_to_big_object();

você precisaria de "auto" ou "const auto"

MyBigObject& ref_to_big_object= big_object;
auto& another_ref = ref_to_big_object;
const auto& yet_another_ref = function_returning_ref_to_big_object();
user2495422
fonte
1

Use autoonde faz sentido que um tipo seja inferido. Se você tem algo que você sabe que é um número inteiro, ou você sabe que é uma string, use int / std :: string, etc. ou ofusca código.

Essa é a minha opinião de qualquer maneira.

LainIwakura
fonte
4
"onde faz sentido ..." é a parte complicada. Os programadores podem ficar tão envolvidos nos detalhes da codificação que perdem a noção do que faz sentido para outros programadores, em particular futuros mantenedores.
darenw
Isso é verdade, embora eu ache fácil dizer quando é ambíguo. Em caso de dúvida, use um comentário!
LainIwakura
1
Não faria sentido usar um tipo?
22411 Mikhail
Alguns desenvolvedores diriam que o tipo sempre deve ser inferido!
Arafangion
Use um comentário ...... ou apenas use o tipo e nenhum comentário é necessário?
user997112 11/06
1

autoA palavra-chave pode ser usada apenas para a variável local, não para argumentos ou membros de classe / estrutura. Portanto, é seguro e viável usá-los em qualquer lugar que você quiser. Eu os uso muito. O tipo é deduzido no momento da compilação, o depurador mostra o tipo durante a depuração, os sizeofrelatórios corretamente, o decltypetipo correto seria - não há nenhum dano. Eu não conto autocomo muito usado, nunca!

Ajay
fonte
0

TL; DR: Veja a regra geral na parte inferior.

A resposta aceita sugere a seguinte regra prática:

Use autosempre que for difícil dizer como escrever o tipo à primeira vista, mas o tipo do lado direito de uma expressão é óbvio.

Mas eu diria que é muito restritivo. Às vezes, não me importo com os tipos, pois a declaração é informativa o suficiente sem que eu me preocupe em dedicar um tempo para descobrir o tipo. O que quero dizer com isso? Considere o exemplo que apareceu em algumas das respostas:

auto x = f();

O que torna isso um exemplo de uso indevido auto? É minha ignorância do f()tipo de retorno? Bem, pode realmente ajudar se eu soubesse, mas - essa não é minha principal preocupação. O que é muito mais problemático é isso xe f()é bastante sem sentido. Se tivéssemos:

auto nugget = mine_gold();

em vez disso, normalmente não me importo se o tipo de retorno da função é óbvio ou não. Lendo a declaração, eu sei o que estou fazendo e sei o suficiente sobre qual é a semântica do valor de retorno para não sentir que também preciso conhecer seu tipo.

Portanto, minha resposta é: Use autosempre que o compilador permitir, a menos que:

  • Você sente que o nome da variável, juntamente com a expressão de inicialização / atribuição, não fornece informações suficientes sobre o que a instrução está fazendo.
  • Você sente que o nome da variável, juntamente com a expressão de inicialização / atribuição, fornece informações "enganosas" sobre qual deve ser o tipo - ou seja, se você tivesse que adivinhar o que vem em vez do automático, seria capaz de adivinhar - e seria errado, e essa suposição falsa tem repercussões posteriormente no código.
  • Você deseja forçar um tipo diferente (por exemplo, uma referência).

E também:

  • Prefira dar um nome significativo (que não contenha o nome do tipo, é claro) antes de substituir autopelo tipo concreto.
einpoklum
fonte
-3

Uma das minhas experiências amargas autoé usá-lo com expressões lambda :

auto i = []() { return 0; };
cout<<"i = "<<i<<endl; // output: 1 !!!

Na verdade, aqui ié resolvido para funcionar como ponteiro de int(*)(). Isso é simples cout, mas imagine que tipo de erros de compilação / tempo de execução incorretos podem causar quando usados template.

Você deve evitar auto essas expressões e colocar um returntipo adequado (ou controlado decltype())

O uso correto para o exemplo acima seria,

auto i = []() { return 0; }(); // and now i contains the result of calling the lambda  
iammilind
fonte
7
Você fez um trabalho terrível ao explicar o que isso tem a ver com o automóvel. Você criou uma função e a imprimiu ... Ok?
Dennis Zickefoose
5
@iammilind: e o que isso tem a ver com o automóvel?
R. Martinho Fernandes
7
Acho altamente improvável que alguém queira colocar ()no final. Os lambdas existem para atuar como funções e é daí que a conversão do ponteiro de função vem. Se você quiser chamá-lo imediatamente, por que usar um lambda? auto i = 0;funciona muito bem.
R. Martinho Fernandes
4
Você pode pelo menos descrever um cenário em que auto x = []() { /* .... whatever goes in here ... */ }()é melhor que auto x = /* .... whatever goes in here ... */;, ou seja, a mesma coisa, sem o lambda? Acho isso inútil, pelo mesmo motivo que não auto x = 42 + y - 42faz sentido.
R. Martinho Fernandes
6
-1 isso não é culpa de auto. O tipo de lambda não pode ser escrito; portanto, é necessário auto , se você esquecer de chamar a função, essa é sua própria atenção! Você pode colocar um ponteiro de função não chamado em uma impressão C da mesma forma aleatoriamente.
spraff 20/09/11