114 votes

En évitant si l'instruction à l'intérieur d'une boucle for?

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

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 avoir de code en double, tout en continuant de se soucier de la performance. Dans la fonction, je suis en train de faire l' if (index) vérifier sur chaque rond de mon for-boucle, même si le résultat est toujours le même. C'est à l'encontre de "se soucier de la performance".

J'ai facilement pu éviter cela en plaçant le vérifier à l'extérieur de mon for-boucle. Cependant, je vais obtenir des charges de code en double:

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

Ce sont donc ces deux "mauvaises" solutions pour moi. Ce que j'ai pensé, est privé de deux fonctions, l'une d'entre eux les aboutissants de l'index, puis appelle les autres. Les autres on ne les aboutissants de la valeur. Cependant, je ne peux pas comprendre comment l'utiliser dans mon programme, j'avais encore besoin de l' if vérifier pour voir un...

Selon le problème, le polymorphisme semble être une bonne solution. Mais je ne vois pas comment l'utiliser ici. Ce qui serait la meilleure façon de résoudre ce genre de problème?

Ce n'est pas un vrai programme, je suis simplement intéressé à apprendre comment ce genre de problème devrait être résolu.

79voto

Ali Points 18740

Passer dans le corps de la boucle, comme un foncteur. Il obtient incorporé au moment de la compilation, pas de perte de performance.

L'idée de passer en ce qui varie, est omniprésent dans la Norme C++ de la Bibliothèque. Il est appelé le modèle de stratégie.

Si vous êtes autorisé à utiliser le 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 obtenez l'idée.

Dans le vieux C++98, il 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 donne l'idée.

38voto

Marc Claesen Points 7203

Dans la fonction, je fais le si (index) vérifiez sur chaque tour de ma boucle for, même si le résultat est toujours le même. C'est à l'encontre de "se soucier de la performance".

Si c'est effectivement le cas, la direction de la prédicteur aura pas de problème dans la prédiction de l' (constant) résultat. En tant que tel, cela ne fera que causer une légère surcharge pour mispredictions dans les premières itérations. C'est rien à craindre en termes de performances

Dans ce cas je plaide pour le maintien de l'essai à l'intérieur de la boucle pour plus de clarté.

35voto

syam Points 9352

Pour développer sur Ali réponse, ce qui est parfaitement correct, mais encore les doublons du code (partie du corps de la boucle, ce n'est malheureusement difficilement évitable lorsque vous utilisez le modèle de stratégie)...

Accordée dans ce cas particulier, la duplication de code n'est pas beaucoup, mais il y a un moyen de réduire encore plus, ce qui est pratique si le corps de la fonction est plus grand que juste un peu d'instructions.

La clé est d'utiliser le compilateur de la capacité à effectuer des constantes / l'élimination du code mort. Nous pouvons le faire manuellement par la cartographie de la valeur d'exécution index au moment de la compilation de la valeur (facile à faire quand il ya seulement un nombre limité de cas, des deux dans ce cas) et l'utilisation d'un non-type de modèle argument qui est connu au moment de la 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 nous retrouvons avec le code compilé qui est équivalent à votre deuxième exemple de code (externe if / inner for), mais sans dupliquer le code nous-mêmes. Maintenant, nous pouvons faire la version du modèle de writeVector aussi compliqué que nous voulons, il y aura toujours un seul morceau de code à maintenir.

Notez la version du modèle (qui prend une constante de compilation sous la forme d'un non-type de l'argument de modèle) et la non-version du modèle (ce qui prend un temps d'exécution variable comme argument de fonction) sont surchargés. Cela vous permet de choisir les plus pertinents de la version selon vos besoins, d'avoir une assez similaires, facile à se souvenir de la syntaxe 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

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