Pourquoi déclarer une variable sur une ligne et l’affecter à la suivante?

101

Je vois souvent dans les codes C et C ++ la convention suivante:

some_type val;
val = something;

some_type *ptr = NULL;
ptr = &something_else;

au lieu de

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

Au départ, j’imaginais que c’était une habitude de l’époque où il fallait déclarer toutes les variables locales en haut de la portée. Mais j’ai appris à ne pas écarter si vite les habitudes des développeurs chevronnés. Alors, y a-t-il une bonne raison de déclarer sur une ligne et d’attribuer ensuite?

Jonathan Sterling
la source
12
+1 pour "j'ai appris à ne pas écarter si vite les habitudes des développeurs vétérans." C'est une sage leçon à apprendre.
Wildcard

Réponses:

92

C

Dans C89, toutes les déclarations devaient se trouver au début d'un scope ( { ... }), mais cette exigence a été rapidement supprimée (d'abord avec les extensions du compilateur et ensuite avec le standard).

C ++

Ces exemples ne sont pas les mêmes. some_type val = something;appelle le constructeur de copie, tandis que val = something;le constructeur par défaut, puis la operator=fonction. Cette différence est souvent critique.

Habitudes

Certaines personnes préfèrent d'abord déclarer les variables et les définir plus tard, dans le cas où elles reformateraient leur code ultérieurement avec les déclarations à un endroit et la définition à un autre.

Concernant les pointeurs, certaines personnes ont juste l'habitude d'initialiser chaque pointeur NULLou nullptrquoi qu'il fasse avec ce pointeur.

orlp
la source
1
Grande distinction pour C ++, merci. Qu'en est-il en plaine C?
Jonathan Sterling
13
Le fait que MSVC ne supporte toujours pas les déclarations, sauf au début d'un bloc lorsqu'il compile en mode C, est pour moi une source d'irritation sans fin.
Michael Burr
5
@ Michael Burr: C'est parce que MSVC ne supporte pas du tout C99.
Orlp
3
"some_type val = quelque chose; appelle le constructeur de copie": il peut appeler le constructeur de copie, mais la norme permet au compilateur d'élider la construction par défaut d'une construction temporaire, la construction de copie de val et la destruction de celui-ci et de construire directement val some_typeconstructeur prenant somethingcomme unique argument. C'est un cas très intéressant et inhabituel en C ++ ... cela signifie qu'il y a une présomption sur la signification sémantique de ces opérations.
2
@Aerovistae: pour les types intégrés, ils sont identiques, mais on ne peut pas toujours en dire autant des types définis par l'utilisateur.
Orlp
27

Vous avez balisé vos questions C et C ++ en même temps, alors que la réponse est très différente dans ces langages.

Premièrement, le libellé du titre de votre question est incorrect (ou, plus précisément, sans pertinence pour la question elle-même). Dans vos deux exemples, la variable est déclarée et définie simultanément, sur une ligne. La différence entre vos exemples réside dans le fait que dans le premier cas, les variables sont laissées non initialisées ou initialisées avec une valeur fictive, puis une valeur significative lui est attribuée ultérieurement. Dans le deuxième exemple, les variables sont immédiatement initialisées .

Deuxièmement, en langage C ++, comme @nightcracker l'a noté dans sa réponse, ces deux constructions sont sémantiquement différentes. Le premier repose sur l'initialisation tandis que le second - sur l'affectation. En C ++, ces opérations sont surchargeables et peuvent donc potentiellement conduire à des résultats différents (bien que l'on puisse noter que produire des surcharges non équivalentes d'initialisation et d'affectation n'est pas une bonne idée).

Dans le langage C standard d'origine (C89 / 90), il est illégal de déclarer des variables au milieu du bloc. C'est pourquoi vous pouvez voir des variables déclarées non initialisées (ou initialisées avec des valeurs nominales) au début du bloc, puis affectées de manière significative. les valeurs plus tard, lorsque ces valeurs significatives deviennent disponibles.

En langage C99, il est correct de déclarer des variables au milieu du bloc (comme en C ++), ce qui signifie que la première approche n'est nécessaire que dans certaines situations spécifiques lorsque l'initialiseur n'est pas connu au moment de la déclaration. (Cela s'applique aussi au C ++).

Fourmi
la source
2
@ Jonathan Sterling: J'ai lu vos exemples. Vous devrez probablement mettre à jour la terminologie standard des langages C et C ++. Plus précisément, sur les termes déclaration et définition , qui ont des significations spécifiques dans ces langues. Je le répète: dans vos deux exemples, les variables sont déclarées et définies sur une seule ligne. En C / C ++, la ligne déclare et définitsome_type val; immédiatement la variable . C'est ce que je voulais dire dans ma réponse. val
1
Je vois ce que tu veux dire là. Vous avez certainement raison de déclarer et de définir le fait que je ne les utilise pas, plutôt dénué de sens. J'espère que vous accepterez mes excuses pour la formulation médiocre et le commentaire mal pensé.
Jonathan Sterling
1
Donc, si le consensus est que «déclarer» est le mauvais mot, je suggérerais à une personne ayant une meilleure connaissance de la norme que moi d’éditer la page Wikibooks.
Jonathan Sterling
2
Dans n'importe quel autre contexte, prononcer serait le mot juste, mais puisque déclarer est un concept bien défini , avec des conséquences, en C et C ++, vous ne pouvez pas l'utiliser aussi librement que dans d'autres contextes.
Orlp
2
@ybungalobill: Vous avez tort. La déclaration et la définition en C / C ++ ne sont pas des concepts mutuellement exclusifs. En réalité, la définition est juste une forme spécifique de déclaration . Chaque définition est une déclaration à la fois (à quelques exceptions près). Il existe des déclarations de définition (c'est-à-dire des définitions) et des déclarations de non définition. De plus, la déclaration thermale est normalement utilisée tout le temps (même s’il s’agit d’une définition), sauf dans les contextes où la distinction entre les deux est critique.
13

Je pense que c'est une vieille habitude, issue de la "déclaration locale". Et donc comme réponse à votre question: Non, je ne pense pas qu'il y ait une bonne raison. Je ne le fais jamais moi-même.


la source
4

J'ai dit quelque chose à ce sujet dans ma réponse à une question d'Helium3 .

En gros, je dis que c'est un moyen visuel de voir facilement ce qui change.

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

et

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

Les autres réponses sont plutôt bonnes. Il y a une certaine histoire autour de cela en C. En C ++, il y a la différence entre un constructeur et un opérateur d'affectation.

Je suis surpris que personne ne mentionne le point supplémentaire: séparer les déclarations de l'utilisation d'une variable peut parfois être beaucoup plus lisible.

Visuellement, lors de la lecture du code, les artefacts les plus banals, tels que les types et les noms de variables, ne vous interpellent pas. Ce sont les déclarations qui vous intéressent le plus, vous passez le plus de temps à regarder, et vous avez donc tendance à jeter un coup d'œil sur le reste.

Si certains types, noms et assignations se déroulent dans le même espace restreint, il y a un peu de surcharge d'informations. De plus, cela signifie que quelque chose d'important se passe dans l'espace sur lequel je regarde habituellement.

Cela peut sembler un peu contre-intuitif, mais c’est un exemple où il peut être meilleur de prendre de la place pour votre source. Je vois cela comme une raison pour laquelle vous ne devriez pas écrire de lignes encombrées qui font des quantités folles d’arithmétique de pointeur et d’assignation dans un espace vertical restreint - ce n’est pas parce que le langage vous laisse échapper de telles choses que vous ne devriez pas le faire. tout le temps. :-)

Asveikau
la source
2

En C, c'était la pratique habituelle, car les variables devaient être déclarées au début de la fonction, contrairement à C ++, où elles pouvaient être déclarées n'importe où dans le corps de la fonction pour être utilisées par la suite. Les pointeurs étaient réglés sur 0 ou sur NULL, car ils s'assuraient simplement que le pointeur ne pointait pas. Autrement, je ne peux penser à aucun avantage important qui oblige quiconque à faire de même.

Vite Falcon
la source
2

Avantages pour la localisation des définitions de variables et leur initialisation significative:

  • si une valeur significative est habituellement attribuée aux variables lorsqu’elles apparaissent pour la première fois dans le code (une autre perspective de la même chose: vous retardez leur apparition jusqu’à ce qu’une valeur significative soit disponible), il n’ya aucune chance qu’elles soient accidentellement utilisées avec une valeur non significative ou non initialisée ( ce qui peut facilement arriver si une initialisation est accidentellement contournée à cause d’énoncés conditionnels, d’une évaluation de court-circuit, d’exceptions, etc.)

  • peut être plus efficace

    • évite les frais généraux liés à la définition de la valeur initiale (construction par défaut ou initialisation sur une valeur sentinelle telle que NULL)
    • operator= peut parfois être moins efficace et nécessiter un objet temporaire
    • parfois (en particulier pour les fonctions en ligne), l’optimiseur peut supprimer certaines / toutes les inefficacités

  • minimiser à son tour la portée des variables minimise le nombre moyen de variables simultanément dans la portée : ceci

    • rend ce plus facile de suivre mentalement les variables de périmètre, les flux d'exécution et les déclarations qui pourraient avoir une incidence sur ces variables, et l'importation de leur valeur
    • au moins pour certains objets complexes et opaques, cela réduit l'utilisation des ressources (tas, threads, mémoire partagée, descripteurs) du programme
  • parfois plus concis que vous ne répétez pas le nom de la variable dans une définition puis dans une affectation significative initiale

  • nécessaire pour certains types tels que les références et lorsque vous souhaitez que l'objet soit const

Arguments pour regrouper les définitions de variables:

  • il est parfois pratique et / ou concis de définir le type de plusieurs variables:

    the_same_type v1, v2, v3;

    (si la raison est simplement que le nom du type est trop long ou complexe, un typedefpeut parfois être meilleur)

  • Parfois, il est souhaitable de regrouper les variables indépendamment de leur utilisation pour mettre en valeur l'ensemble des variables (et des types) impliqués dans certaines opérations:

    type v1;
    type v2; type v3;

    Cela met l'accent sur les caractères communs des types et facilite un peu leur modification, tout en restant fidèle à une variable par ligne facilitant le copier-coller, les //commentaires, etc.

Comme souvent dans la programmation, bien qu’une pratique puisse présenter des avantages empiriques évidents dans la plupart des situations, l’autre pratique peut être extrêmement améliorée dans quelques cas.

Tony
la source
J'aimerais que davantage de langues distinguent le cas où code déclare et définit la valeur d'une variable qui ne serait jamais écrite ailleurs, bien que de nouvelles variables puissent utiliser le même nom [c'est-à-dire, le comportement serait le même, que les instructions ultérieures utilisent la même variable ou un autre], à partir de ceux où le code crée une variable qui doit être accessible en écriture à plusieurs endroits. Bien que les deux cas d'utilisation s'exécutent de la même manière, il est très utile de savoir quand les variables peuvent changer est très utile lorsque vous essayez de localiser des bogues.
Supercat