Por que declarar uma variável em uma linha e atribuí-la na próxima?

101

Muitas vezes vejo nos códigos C e C ++ a seguinte convenção:

some_type val;
val = something;

some_type *ptr = NULL;
ptr = &something_else;

ao invés de

some_type val = something;
some_type *ptr = &something_else;

Inicialmente, assumi que esse era um hábito que restava desde os dias em que você tinha que declarar todas as variáveis ​​locais no topo do escopo. Mas aprendi a não descartar tão rapidamente os hábitos dos desenvolvedores veteranos. Então, há uma boa razão para declarar em uma linha e atribuir depois?

Jonathan Sterling
fonte
12
+1 em "Aprendi a não descartar tão rapidamente os hábitos de desenvolvedores veteranos". Essa é uma lição sábia para aprender.
Curinga

Respostas:

92

C

No C89, todas as declarações tinham que estar no início de um escopo ( { ... }), mas esse requisito foi eliminado rapidamente (primeiro com extensões do compilador e depois com o padrão).

C ++

Esses exemplos não são os mesmos. some_type val = something;chama o construtor de cópia, enquanto val = something;chama o construtor padrão e, em seguida, a operator=função Essa diferença é frequentemente crítica.

Hábitos

Algumas pessoas preferem primeiro declarar variáveis ​​e depois defini-las, caso estejam reformatando seu código posteriormente com as declarações em um ponto e a definição em outro.

Sobre os ponteiros, algumas pessoas têm o hábito de inicializar todos os ponteiros NULLou nullptr, não importa o que façam com esse ponteiro.

orlp
fonte
1
Grande distinção para C ++, obrigado. E na planície C?
Jonathan Sterling
13
O fato de o MSVC ainda não suportar declarações, exceto no início de um bloco, quando está compilando no modo C, é a fonte de irritação sem fim para mim.
Michael Burr
5
@ Michael Burr: Isso ocorre porque o MSVC não suporta C99.
orlp
3
"some_type val = alguma coisa; chama o construtor de cópia": pode chamar o construtor de cópia, mas o Padrão permite que o compilador elide a construção padrão de um tempory, copie a construção de val e destrua o temporário e construa diretamente val usando um some_typeconstrutor tomando somethingcomo único argumento. Este é um caso de borda muito interessante e incomum em C ++ ... significa que há uma presunção sobre o significado semântico dessas operações.
2
@ Aerovistae: para tipos internos, eles são iguais, mas nem sempre é possível dizer o mesmo para tipos definidos pelo usuário.
orlp
27

Você marcou sua pergunta C e C ++ ao mesmo tempo, enquanto a resposta é significativamente diferente nesses idiomas.

Em primeiro lugar, a redação do título da sua pergunta está incorreta (ou, mais precisamente, irrelevante para a própria pergunta). Nos dois exemplos, a variável é declarada e definida simultaneamente, em uma linha. A diferença entre seus exemplos é que, no primeiro, as variáveis ​​são deixadas não inicializadas ou inicializadas com um valor fictício e, em seguida, é atribuído um valor significativo posteriormente. No segundo exemplo, as variáveis ​​são inicializadas imediatamente.

Em segundo lugar, na linguagem C ++, como o @nightcracker observou em sua resposta, essas duas construções são semanticamente diferentes. O primeiro depende da inicialização, enquanto o segundo - da atribuição. Em C ++, essas operações são sobrecarregáveis ​​e, portanto, podem potencialmente levar a resultados diferentes (embora se possa observar que produzir sobrecargas não equivalentes de inicialização e atribuição não é uma boa idéia).

Na linguagem C padrão original (C89 / 90), é ilegal declarar variáveis ​​no meio do bloco, e é por isso que você pode ver variáveis ​​declaradas não inicializadas (ou inicializadas com valores fictícios) no início do bloco e depois atribuídas significativas valores posteriormente, quando esses valores significativos estiverem disponíveis.

Na linguagem C99, não há problema em declarar variáveis ​​no meio do bloco (assim como em C ++), o que significa que a primeira abordagem é necessária apenas em algumas situações específicas quando o inicializador não é conhecido no ponto de declaração. (Isso também se aplica ao C ++).

Formiga
fonte
2
@ Jonathan Sterling: Eu li seus exemplos. Você provavelmente precisará atualizar a terminologia padrão das linguagens C e C ++. Especificamente, nos termos declaração e definição , que têm significados específicos nessas línguas. Vou repetir novamente: nos dois exemplos, as variáveis ​​são declaradas e definidas em uma linha. Em C / C ++, a linha some_type val;imediatamente declara e define a variável val. Foi isso que eu quis dizer na minha resposta.
1
Entendo o que você quer dizer aí. Você definitivamente tem razão em declarar e definir como sendo sem sentido da maneira como as usei. Espero que você aceite minhas desculpas pelas palavras erradas e pelos comentários mal pensados.
Jonathan Sterling
1
Portanto, se o consenso é que “declarar” é a palavra errada, sugiro que alguém com um conhecimento melhor do padrão do que eu edite a página do Wikilivros.
Jonathan Sterling
2
Em qualquer outro contexto, declarar seria a palavra certa, mas como declarar é um conceito bem definido , com consequências, em C e C ++, você não pode usá-lo da maneira mais vaga possível em outros contextos.
orlp
2
@ybungalobill: Você está errado. Declaração e definição em C / C ++ não são conceitos mutuamente exclusivos. Na verdade, a definição é apenas uma forma específica de declaração . Toda definição é uma declaração ao mesmo tempo (com poucas exceções). Existem declarações definidoras (ou seja, definições) e declarações não definidoras. Além disso, normalmente a declaração térmica é usada o tempo todo (mesmo que seja uma definição), exceto nos contextos em que a distinção entre os dois é crítica.
13

Eu acho que é um hábito antigo, sobra dos tempos da "declaração local". E, portanto, como resposta à sua pergunta: Não, acho que não há uma boa razão. Eu nunca faço isso sozinho.


fonte
4

Eu disse algo sobre isso na minha resposta a uma pergunta do Helium3 .

Basicamente, eu digo que é uma ajuda visual ver facilmente o que mudou.

if (a == 0) {
    struct whatever *myobject = 0;
    /* did `myobject` (the pointer) get assigned?
    ** or was it `*myobject` (the struct)? */
}

e

if (a == 0) {
    struct whatever *myobject;
    myobject = 0;
    /* `myobject` (the pointer) got assigned */
}
pmg
fonte
4

As outras respostas são muito boas. Há alguma história em torno disso em C. No C ++, há a diferença entre um construtor e um operador de atribuição.

Estou surpreso que ninguém mencione o ponto adicional: manter as declarações separadas do uso de uma variável às vezes pode ser muito mais legível.

Visualmente falando, ao ler código, os artefatos mais mundanos, como os tipos e nomes de variáveis, não são o que chamam a atenção. É nas frases em que você mais se interessa, passa mais tempo olhando e, portanto, há uma tendência a olhar para o resto.

Se eu tenho alguns tipos, nomes e tarefas todos no mesmo espaço, é uma sobrecarga de informações. Além disso, significa que algo importante está acontecendo no espaço que eu geralmente olho.

Pode parecer um pouco contra-intuitivo dizer, mas esse é um exemplo em que fazer com que sua fonte ocupe mais espaço vertical pode torná-lo melhor. Eu vejo isso como um motivo pelo qual você não deve escrever linhas cheias de atolamentos que fazem quantidades loucas de aritmética e atribuição de ponteiros em um espaço vertical apertado - só porque o idioma permite que você se dê bem com essas coisas não significa que você deve fazer isso o tempo todo. :-)

asveikau
fonte
2

Em C, essa era a prática padrão porque as variáveis ​​tinham que ser declaradas no início da função, diferentemente do C ++, onde podiam ser declaradas em qualquer lugar do corpo da função para serem usadas posteriormente. Os ponteiros foram definidos como 0 ou NULL, porque apenas garantiram que o ponteiro não apontasse lixo. Caso contrário, não há vantagem significativa em que eu possa pensar, o que leva qualquer pessoa a fazer assim.

Vite Falcon
fonte
2

Prós por localizar definições de variáveis ​​e sua inicialização significativa:

  • se as variáveis ​​recebem habitualmente um valor significativo quando aparecem pela primeira vez no código (outra perspectiva da mesma coisa: você atrasa a aparência até que um valor significativo esteja disponível), então não há chance delas serem acidentalmente usadas com um valor sem significado ou não inicializado ( o que pode acontecer facilmente é que alguma inicialização é ignorada acidentalmente devido a declarações condicionais, avaliação de curto-circuito, exceções etc.)

  • pode ser mais eficiente

    • evita despesas gerais de definir o valor inicial (construção ou inicialização padrão para algum valor de sentinela como NULL)
    • operator= Às vezes, pode ser menos eficiente e exigir um objeto temporário
    • algumas vezes (especialmente para funções em linha), o otimizador pode remover algumas / todas as ineficiências

  • minimizar o escopo de variáveis, por sua vez, minimiza o número médio de variáveis ​​simultaneamente no escopo : isso

    • facilita o rastreamento mental das variáveis ​​no escopo, os fluxos de execução e instruções que podem afetar essas variáveis ​​e a importação de seu valor
    • pelo menos para alguns objetos complexos e opacos, isso reduz o uso de recursos (heap, threads, memória compartilhada, descritores) do programa
  • às vezes, mais conciso, pois você não está repetindo o nome da variável em uma definição e em uma atribuição significativa inicial

  • necessário para certos tipos, como referências e quando você deseja que o objeto seja const

Argumentos para agrupar definições de variáveis:

  • às vezes é conveniente e / ou conciso fatorar o tipo de um número de variáveis:

    the_same_type v1, v2, v3;

    (se o motivo for o fato de o nome do tipo ser muito longo ou complexo, typedefàs vezes pode ser melhor)

  • às vezes é desejável agrupar variáveis ​​independentemente de seu uso para enfatizar o conjunto de variáveis ​​(e tipos) envolvidos em alguma operação:

    type v1;
    type v2; type v3;

    Isso enfatiza a semelhança de tipo e facilita um pouco sua alteração, mantendo uma variável por linha que facilita copiar e colar, //comentar etc.

Como geralmente acontece na programação, embora possa haver um benefício empírico claro para uma prática na maioria das situações, a outra prática pode realmente ser extremamente melhor em alguns casos.

Tony
fonte
Eu gostaria que mais idiomas distinguissem o caso em que o código declara e define o valor de uma variável que nunca seria escrita em outro lugar, embora novas variáveis ​​possam usar o mesmo nome [ou seja, onde o comportamento seria o mesmo, se as instruções posteriores usassem a mesma variável ou um diferente], daqueles onde o código cria uma variável que deve ser gravável em vários locais. Embora ambos os casos de uso sejam executados da mesma maneira, saber quando as variáveis ​​podem mudar é muito útil ao tentar rastrear erros.
Supercat