web-dev-qa-db-fra.com

Éviter si déclaration dans une boucle for?

J'ai une classe appelée Writer qui a une fonction writeVector comme ceci:

void Drawer::writeVector(vector<T> vec, bool index=true)
{
    for (unsigned int i = 0; i < vec.size(); i++) {
        if (index) {
            cout << i << "\t";
        }
        cout << vec[i] << "\n";
    }
}

J'essaie de ne pas créer de code en double tout en m'inquiétant de la performance . Dans la fonction, je vérifie if (index) à chaque tour de ma boucle for, même si le résultat est toujours identique. Ceci est contre "se soucier de la performance".

Je pourrais facilement éviter cela en plaçant le contrôle en dehors de ma for- loop . Cependant, je vais avoir beaucoup de code en double:

void Drawer::writeVector(...)
{
    if (index) {
        for (...) {
            cout << i << "\t" << vec[i] << "\n";
        }
    }
    else {
        for (...) {
            cout << vec[i] << "\n";
        }
    }
}

Il s’agit donc de deux "mauvaises" solutions pour moi ... Je pense à deux fonctions privées, l’une sortant de l’index, puis appelant l´autre ...__, l´autre excluant seulement la valeur .. Cependant, je ne sais pas comment l'utiliser avec mon programme, il me faudrait quand même le chèque if pour savoir lequel appeler ...

Selon le problème, le polymorphisme semble être une solution correcte… .. Mais je ne vois pas comment l'utiliser ici… .. Quel serait le moyen privilégié de résoudre ce type de problème?

Ce n'est pas un vrai programme, Je voudrais juste savoir comment résoudre ce genre de problème.

113
Skamah One

Passe dans le corps de la boucle en tant que foncteur. Il est en ligne au moment de la compilation, pas de pénalité de performance. 

L'idée de transmettre ce qui varie est omniprésente dans la bibliothèque standard C++. C'est ce qu'on appelle le modèle de stratégie.

Si vous êtes autorisé à utiliser C++ 11, vous pouvez faire quelque chose comme ceci:

#include <iostream>
#include <set>
#include <vector>

template <typename Container, typename Functor, typename Index = std::size_t>
void for_each_indexed(const Container& c, Functor f, Index index = 0) {

    for (const auto& e : c)
        f(index++, e);
}

int main() {

    using namespace std;

    set<char> s{'b', 'a', 'c'};

    // indices starting at 1 instead of 0
    for_each_indexed(s, [](size_t i, char e) { cout<<i<<'\t'<<e<<'\n'; }, 1u);

    cout << "-----" << endl;

    vector<int> v{77, 88, 99};

    // without index
    for_each_indexed(v, [](size_t , int e) { cout<<e<<'\n'; });
}

Ce code n'est pas parfait mais vous en avez l'idée.

Dans l'ancien C++ 98, cela ressemble à ceci:

#include <iostream>
#include <vector>
using namespace std;

struct with_index {
  void operator()(ostream& out, vector<int>::size_type i, int e) {
    out << i << '\t' << e << '\n';
  }
};

struct without_index {
  void operator()(ostream& out, vector<int>::size_type i, int e) {
    out << e << '\n';
  }
};


template <typename Func>
void writeVector(const vector<int>& v, Func f) {
  for (vector<int>::size_type i=0; i<v.size(); ++i) {
    f(cout, i, v[i]);
  }
}

int main() {

  vector<int> v;
  v.Push_back(77);
  v.Push_back(88);
  v.Push_back(99);

  writeVector(v, with_index());

  cout << "-----" << endl;

  writeVector(v, without_index());

  return 0;
}

Encore une fois, le code est loin d’être parfait mais il vous en donne l’idée.

78
Ali

Dans la fonction, je vérifie si (index) chaque tour de ma boucle for, même si le résultat est toujours le même. Ceci est contre "se soucier de la performance".

Si c'est bien le cas, le prédicteur de branche n'aura aucun problème pour prédire le résultat (constant). En tant que tel, cela ne provoquera qu'une légère surcharge pour les erreurs de prédiction des premières itérations. Rien à craindre en termes de performance

Dans ce cas, je préconise de garder le test dans la boucle pour plus de clarté.

38
Marc Claesen

Pour développer la réponse de ALi, qui est parfaitement correcte mais qui duplique encore du code (une partie du corps de la boucle, cela est malheureusement difficilement évitable lors de l'utilisation du modèle de stratégie) ...

Certes, dans ce cas particulier, la duplication de code n’est pas grande chose, mais il existe un moyen de la réduire encore plus, ce qui est pratique si le corps de la fonction est plus gros que quelques instructions .

La clé consiste à utiliser la capacité du compilateur à exécuter le pliage constant/l'élimination du code mort . Nous pouvons le faire en mappant manuellement la valeur d'exécution de index à une valeur au moment de la compilation (facile à faire lorsque le nombre de cas est limité - deux dans ce cas) et en utilisant un argument de modèle non typé connu à temps de compilation:

template<bool index = true>
//                  ^^^^^^ note: the default value is now part of the template version
//                         see below to understand why
void writeVector(const vector<int>& vec) {
    for (size_t i = 0; i < vec.size(); ++i) {
        if (index) { // compile-time constant: this test will always be eliminated
            cout << i << "\t"; // this will only be kept if "index" is true
        }
        cout << vec[i] << "\n";
    }
}

void writeVector(const vector<int>& vec, bool index)
//                                            ^^^^^ note: no more default value, otherwise
//                                            it would clash with the template overload
{
    if (index) // runtime decision
        writeVector<true>(vec);
        //          ^^^^ map it to a compile-time constant
    else
        writeVector<false>(vec);
}

De cette façon, nous obtenons un code compilé qui équivaut à votre deuxième exemple de code (if extérieur/intérieur for), mais sans dupliquer le code nous-mêmes. Nous pouvons maintenant rendre la version de modèle de writeVector aussi compliquée que nous le souhaitons, il y aura toujours un seul élément de code à gérer.

Notez comment la version du modèle (qui prend une constante à la compilation sous la forme d'un argument de modèle non-type) et la version non-modèle (qui prend une variable d'exécution en tant qu'argument de fonction) sont surchargées. Cela vous permet de choisir la version la plus pertinente en fonction de vos besoins, avec une syntaxe assez similaire et facile à retenir dans les deux cas:

writeVector<true>(vec);   // you already know at compile-time which version you want
                          // no need to go through the non-template runtime dispatching

writeVector(vec, index);  // you don't know at compile-time what "index" will be
                          // so you have to use the non-template runtime dispatching

writeVector(vec);         // you can even use your previous syntax using a default argument
                          // it will call the template overload directly
35
syam

Dans la plupart des cas, votre code est déjà bon pour la performance et la lisibilité. Un bon compilateur est capable de détecter les invariants de boucle et de procéder aux optimisations appropriées. Prenons l'exemple suivant, très proche de votre code:

#include <cstdio>
#include <iterator>

void write_vector(int* begin, int* end, bool print_index = false) {
    unsigned index = 0;
    for(int* it = begin; it != end; ++it) {
        if (print_index) {
            std::printf("%d: %d\n", index, *it);
        } else {
            std::printf("%d\n", *it);
        }
        ++index;
    }
}

int my_vector[] = {
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
};


int main(int argc, char** argv) {
    write_vector(std::begin(my_vector), std::end(my_vector));
}

J'utilise la ligne de commande suivante pour le compiler:

g++ --version
g++ (GCC) 4.9.1
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
g++ -O3 -std=c++11 main.cpp

Ensuite, vidons Assembly:

objdump -d a.out | c++filt > main.s

Le résultat Assembly de write_vector est:

00000000004005c0 <write_vector(int*, int*, bool)>:
  4005c0:   48 39 f7                cmp    %rsi,%rdi
  4005c3:   41 54                   Push   %r12
  4005c5:   49 89 f4                mov    %rsi,%r12
  4005c8:   55                      Push   %rbp
  4005c9:   53                      Push   %rbx
  4005ca:   48 89 fb                mov    %rdi,%rbx
  4005cd:   74 25                   je     4005f4 <write_vector(int*, int*, bool)+0x34>
  4005cf:   84 d2                   test   %dl,%dl
  4005d1:   74 2d                   je     400600 <write_vector(int*, int*, bool)+0x40>
  4005d3:   31 ed                   xor    %ebp,%ebp
  4005d5:   0f 1f 00                nopl   (%rax)
  4005d8:   8b 13                   mov    (%rbx),%edx
  4005da:   89 ee                   mov    %ebp,%esi
  4005dc:   31 c0                   xor    %eax,%eax
  4005de:   bf a4 06 40 00          mov    $0x4006a4,%edi
  4005e3:   48 83 c3 04             add    $0x4,%rbx
  4005e7:   83 c5 01                add    $0x1,%ebp
  4005ea:   e8 81 fe ff ff          callq  400470 <printf@plt>
  4005ef:   49 39 dc                cmp    %rbx,%r12
  4005f2:   75 e4                   jne    4005d8 <write_vector(int*, int*, bool)+0x18>
  4005f4:   5b                      pop    %rbx
  4005f5:   5d                      pop    %rbp
  4005f6:   41 5c                   pop    %r12
  4005f8:   c3                      retq   
  4005f9:   0f 1f 80 00 00 00 00    nopl   0x0(%rax)
  400600:   8b 33                   mov    (%rbx),%esi
  400602:   31 c0                   xor    %eax,%eax
  400604:   bf a8 06 40 00          mov    $0x4006a8,%edi
  400609:   48 83 c3 04             add    $0x4,%rbx
  40060d:   e8 5e fe ff ff          callq  400470 <printf@plt>
  400612:   49 39 dc                cmp    %rbx,%r12
  400615:   75 e9                   jne    400600 <write_vector(int*, int*, bool)+0x40>
  400617:   eb db                   jmp    4005f4 <write_vector(int*, int*, bool)+0x34>
  400619:   0f 1f 80 00 00 00 00    nopl   0x0(%rax)

Nous pouvons voir qu’au début de la fonction, nous vérifions la valeur et passons à l’une des deux boucles possibles:

  4005cf:   84 d2                   test   %dl,%dl
  4005d1:   74 2d                   je     400600 <write_vector(int*, int*, bool)+0x40>

Bien entendu, cela ne fonctionne que si un compilateur est capable de détecter qu'une condition est invariante. En général, cela fonctionne parfaitement pour les drapeaux et les fonctions simples en ligne. Mais si la condition est "complexe", envisagez d'utiliser des approches provenant d'autres réponses.

0
ivaigult