45 votes

Existe-t-il une base pour cette alternative "pour" la syntaxe de boucle?

Je suis tombé sur une série de diapositives pour une diatribe parler sur le C++. Il y avait quelques détails intéressants ici et là, mais diapositive 8 se présentait à moi. Son contenu a été, environ:

Constante évolution styles

Vieux et cassé:

for (int i = 0; i < n; i++)

Nouveau hotness:

for (int i(0); i != n; ++i)

Je n'avais jamais vu un for boucle à l'aide de la deuxième forme avant, de sorte que l'affirmation qu'il était le "Nouveau hotness" m'ont intéressé. Je peux voir quelques justification:

  1. Directe d'initialisation à l'aide d'un constructeur vs copie d'initialisation
  2. != pourrait être plus rapide dans le matériel qu' <
  3. ++i ne nécessite pas le compilateur de garder l'ancienne valeur de i environ, ce qui est le i++ le ferait.

J'imagine que c'est de l'optimisation prématurée, cependant, que les modernes l'optimisation des compilateurs de dresser les deux vers le bas pour les mêmes instructions; si quoi que ce soit, ce dernier est pire parce qu'il n'est pas "normal" for boucle. À l'aide de != au lieu de < est également suspect pour moi, parce qu'il fait la boucle sémantiquement différente de la version originale, et peut conduire à quelques rares, mais intéressant, de bugs.

Y avait-il un point où la "Nouvelle hotness" version de l' for boucle était populaire? Est-il une raison pour utiliser cette version de ces jours (2016+), par exemple inhabituel de la boucle types de variables?

74voto

Barry Points 45207
  1. Directe d'initialisation à l'aide d'un constructeur vs copie d'initialisation

    Ce sont exactement identiques pour ints et va générer un code identique. Utilisation selon celle que vous préférez lire ou ce que votre code de politiques, etc.

  2. != pourrait être plus rapide dans le matériel qu' <

    Le code généré ne sera pas réellement être i < n vs i != n, ça va être comme i - n < 0 vs i - n == 0. Qui est, vous obtiendrez un jle dans le premier cas et un je dans le second cas. Tous les jcc instructions ont des performances identiques (voir l'instruction de référence et optionization de référence, qui vient de la liste de tous les jcc instructions comme ayant un débit de 0.5).

    Qui est le meilleur? Pour ints, n'a probablement pas d'importance en terme de performance.

    Strictement plus sûr de le faire < dans le cas où vous souhaitez ignorer les éléments dans le milieu, car alors vous n'avez pas à vous soucier de se retrouver avec un infini/undefined boucle. Mais il suffit d'écrire la condition qui fait le plus de sens d'écrire avec la boucle que vous avez écrit. Jetez aussi un oeil à dasblinkenlight de réponse.

  3. ++i ne nécessite pas le compilateur de garder l'ancienne valeur de i environ, ce qui est le i++ le ferait.

    Ouais c'est un non-sens. Si votre compilateur ne peut pas dire que vous n'avez pas besoin de l'ancienne valeur et seulement réécrire l' i++ de ++i, puis obtenir un nouveau compilateur. Ces sera certainement compiler le même code avec des performances identiques.

    Cela dit, c'est une bonne ligne de conduite de l'utiliser juste la bonne chose. Vous voulez incrémenter i, de sorte que l' ++i. Utilisez uniquement la post-incrémentation lorsque vous avez besoin d'utiliser la post-incrémentation. L'arrêt complet.


Cela dit, le vrai "nouveau hotness" serait certainement:

for (int i : range(n)) { ... }

22voto

dasblinkenlight Points 264350

Vous avez raison sur l'optimisation des compilateurs et des préfixes vs postfix ++ de l'opérateur. Il n'a pas d'importance pour un int, mais il est plus important lorsque vous utilisez des itérateurs.

La deuxième partie de votre question qui est plus intéressant:

À l'aide de != au lieu de < est également suspect pour moi, parce qu'il fait la boucle sémantiquement différente de la version originale, et peut conduire à quelques rares, mais intéressant, de bugs.

Je voudrais reformuler comme "peut attraper quelques rares, mais intéressant, de bugs."

Une façon simple de débattre de ce point a été offert par Dijkstra dans son livre Une Discipline de Programmation. Il a souligné qu'il est plus facile de raisonner sur des boucles de renforcer les post-conditions, c'est à la raison sur les boucles avec les plus faibles de post-conditions. Car le post-condition de la boucle est l'inverse de sa poursuite condition, on doit préférer les boucles avec les plus faibles de la poursuite conditions.

a != b est plus faible que a < bcar a < b implique qu' a != b, mais a != b ne signifie pas qu' a < b. Par conséquent, a != b fait un meilleur maintien de la condition.

En termes très simples, vous savez qu' a == b immédiatement après la boucle avec a != b ; d'autre part, lorsque la boucle avec a < b est plus, tout ce que vous savez est que l' a >= b, ce qui n'est pas aussi bon que d'en connaître l'exacte égalité.

13voto

Rakete1111 Points 10248

Personnellement, je n'aime pas la deuxième, je voudrais utiliser:

for (int i = 0; i < n; ++i); //A combination of the two :)

int i = 0 vs int i(0)

Aucune différence que ce soit, ils ont même compiler les mêmes instructions de montage (pas d'optimisations):

int main()
{
    int i = 0; //int i(0);
}

int i = 0 version:

main:
    pushq   %rbp
    movq    %rsp, %rbp
    movl    $0, -4(%rbp)
    movl    $0, %eax
    popq    %rbp
    ret

int i(0) version:

main:
    pushq   %rbp
    movq    %rsp, %rbp
    movl    $0, -4(%rbp)
    movl    $0, %eax
    popq    %rbp
    ret

i < n vs i != n

Vous avez raison que l' != peut introduire certains intéressant de bugs:

for (int i = 0; i != 3; ++i)
{
    if (i == 2)
        i += 2; //Oops, infinite loop
    //...
}

L' != comparaison est principalement utilisé pour les itérateurs, qui ne définissent pas un < (ou >) de l'opérateur. Peut-être que c'est ce que l'auteur voulait dire?

Mais ici, la deuxième version est nettement mieux, comme ça plus clairement l'intention de l'autre (et présente moins de bugs).


i++ vs ++i

Pour les types intégrés (et d'autres trivial types), comme int, il n'y a pas de différence, comme le compilateur d'optimiser le retour temporaire des valeurs. Ici, encore une fois, certains des itérateurs sont chers, et donc la création et la destruction pourrait être un gain de performance.

Mais qui vraiment n'a pas d'importance dans ce cas, que même sans les optimisations qu'ils émettent de la même assemblée de sortie!

int main()
{
    int i = 0;
    i++; //++i;
}

i++ version:

main:
    pushq   %rbp
    movq    %rsp, %rbp
    movl    $0, -4(%rbp)
    addl    $1, -4(%rbp)
    movl    $0, %eax
    popq    %rbp
    ret

++i version:

main:
    pushq   %rbp
    movq    %rsp, %rbp
    movl    $0, -4(%rbp)
    addl    $1, -4(%rbp)
    movl    $0, %eax
    popq    %rbp
    ret

7voto

user3721426 Points 1

Les deux formes sont en aucune façon liés à la performance. Ce qui importe est de savoir comment écrire le code en général. Suivent les modèles semblables et de se concentrer sur l'expressivité et de la concision. Donc pour l'initialisation, préférez int i(0) (ou mieux: i{0}), car cela souligne le fait que c'est une phase d'initialisation, pas une cession. Pour la comparaison, la différence entre les deux != et < c'est que vous mettez moins d'exigences sur votre type. Pour les entiers il n'y a pas de différence, mais en général les itérateurs ne gère pas moins de, mais il faut toujours soutenir l'égalité. Enfin, préfixe incrément exprime votre intention mieux parce que vous n'utilisez pas le résultat.

3voto

Matt McNabb Points 14273

Dans ce code, il ne fait aucune différence. Mais je suppose que ce que l'auteur s'est fixé pour objectif consiste à utiliser le même style de style de codage pour l'ensemble de l' for boucles (à l'exception de gamme, sans doute).

Par exemple si on a une autre classe que le compteur de boucle, de type

for ( Bla b = 0; b < n; b++ )

ensuite, il y a des problèmes:

  • Bla b = 0; peut ne pas compiler si Bla n'ont pas accessible copier/déplacer constructeur
  • b < n peut ne pas compiler si Bla n'admet pas definiig une faiblesse de la commande, ou de définir des operator<.
  • b++ peut ne pas compiler ou ont des effets secondaires indésirables, car le post-incrémentation renvoie généralement par valeur

À l'aide de l'écrivain suggéré par le modèle, les exigences sur une boucle itérateur sont moindres.


Notez que cette discussion peut se poursuivre indéfiniment. On pourrait dire int i{}; est mieux parce que certains types peuvent ne pas admettre 0 comme initialiseur. Ensuite, nous pourrions dire, et bien, si n n'était pas un int? Il faut vraiment être decltype(n) i{}. Ou en fait, nous devrions utiliser une gamme de boucles qui résout tous les problèmes ci-dessus. Et ainsi de suite.

Ainsi, à la fin de la journée, il est toujours question de préférence personnelle.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X