97 votes

Nettoyer les façons d’écrire les multiples ' pour ' boucles

Pour un tableau à plusieurs dimensions, nous avons généralement besoin d’écrire un `` boucle pour chacune de ses dimensions. Par exemple :

Vous voyez ce genre de boucles dans notre code fréquemment. Comment utiliser les macros pour définir la boucles afin que je n’avez pas besoin de ré-écrire ce genre de code chaque fois ? Y a-t-il une meilleure façon de le faire ?

280voto

James Kanze Points 96599

La première chose, c'est que vous n'utilisez pas une telle structure de données. Si vous avez besoin d'une des trois dimensions de la matrice, vous définissez un:

class Matrix3D
{
    int x;
    int y;
    int z;
    std::vector<int> myData;
public:
    //  ...
    int& operator()( int i, int j, int k )
    {
        return myData[ ((i * y) + j) * z + k ];
    }
};

Ou si vous voulez de l'index à l'aide de [][][], vous avez besoin d'un operator[] qui renvoie un proxy.

Une fois que vous avez fait cela, si vous trouvez que vous avez en permanence à itérer comme vous l'avez présenté, vous exposer un itérateur qui l'appui:

class Matrix3D
{
    //  as above...
    typedef std::vector<int>::iterator iterator;
    iterator begin() { return myData.begin(); }
    iterator end()   { return myData.end();   }
};

Ensuite, il vous suffit d'écrire:

for ( Matrix3D::iterator iter = m.begin(); iter != m.end(); ++ iter ) {
    //  ...
}

(ou simplement:

for ( auto& elem: m ) {
}

si vous avez le C++11.)

Et si vous avez besoin de trois indices au cours de ces itérations, c'est possible de créer un itérateur qui les expose:

class Matrix3D
{
    //  ...
    class iterator : private std::vector<int>::iterator
    {
        Matrix3D const* owner;
    public:
        iterator( Matrix3D const* owner,
                  std::vector<int>::iterator iter )
            : std::vector<int>::iterator( iter )
            , owner( owner )
        {
        }
        using std::vector<int>::iterator::operator++;
        //  and so on for all of the iterator operations...
        int i() const
        {
            ((*this) -  owner->myData.begin()) / (owner->y * owner->z);
        }
        //  ...
    };
};

43voto

Jefffrey Points 31698

Utiliser une macro pour cacher la `` boucles peuvent être beaucoup déroutants, seulement sauver quelques caractères. J’utiliserais gamme-pour à la place des boucles :

Bien sûr vous pouvez remplacer avec si vous n’êtes, en fait, pas modifier les données.

21voto

fasked Points 2226

Quelque chose comme ceci peut aider:

 template <typename Container, typename Function>
 void for_each3d(const Container &container, Function function)
 {
     for (const auto &i: container)
         for (const auto &j: i)
             for (const auto &k: j)
                 function(k);
 }

 int main()
 {
     vector< vector< vector<int> > > A;     
     for_each3d(A, [](int i){ std::cout << i << std::endl; });

     double B[10][8][5] = { /* ... */ };
     for_each3d(B, [](double i){ std::cout << i << std::endl; });
 }

Afin de rendre la N-aire nous avons besoin de quelques modèles de la magie. Tout d'abord il faut créer SFINAE structure de distinguer si cette valeur ou d'un conteneur. L'implémentation par défaut pour les valeurs, et de spécialisations pour les tableaux et chacun de ces types de conteneurs. Comment @Zeta notes, nous pouvons déterminer la norme de conteneurs par la imbriquée iterator type (idéalement, nous devrions vérifier si le type peut être utilisé avec la gamme de base- for ou pas).

 template <typename T>
 struct has_iterator
 {
     template <typename C>
     constexpr static std::true_type test(typename C::iterator *);

     template <typename>
     constexpr static std::false_type test(...);

     constexpr static bool value = std::is_same<
         std::true_type, decltype(test<typename std::remove_reference<T>::type>(0))
     >::value;
 };

 template <typename T>
 struct is_container : has_iterator<T> {};

 template <typename T>
 struct is_container<T[]> : std::true_type {};

 template <typename T, std::size_t N>
 struct is_container<T[N]> : std::true_type {}; 

 template <class... Args>
 struct is_container<std::vector<Args...>> : std::true_type {};

La mise en œuvre de l' for_each est simple. La fonction par défaut appellera function:

 template <typename Value, typename Function>
 typename std::enable_if<!is_container<Value>::value, void>::type
 rfor_each(const Value &value, Function function)
 {
     function(value);
 }

Et la spécialisation s'appeler lui-même de manière récursive:

 template <typename Container, typename Function>
 typename std::enable_if<is_container<Container>::value, void>::type
 rfor_each(const Container &container, Function function)
 {
     for (const auto &i: container)
         rfor_each(i, function);
 }

Et voila:

 int main()
 {
     using namespace std;
     vector< vector< vector<int> > > A;
     A.resize(3, vector<vector<int> >(3, vector<int>(3, 5)));
     rfor_each(A, [](int i){ std::cout << i << ", "; });
     // 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,

     std::cout << std::endl;
     double B[3][3] = { { 1. } };
     rfor_each(B, [](double i){ std::cout << i << ", "; });
     // 1, 0, 0, 0, 0, 0, 0, 0, 0,
 }

Aussi cela ne fonctionnera pas pour les pointeurs (tableaux alloués dans le tas).

17voto

kuroi neko Points 3902

La plupart des réponses simplement montrer comment C++ peut être tordu dans incompréhensible syntaxique des extensions, à mon humble avis.

En définissant ce que des modèles ou des macros, vous venez de force d'autres programmeurs à comprendre les bits de l'obfuscation de code conçu pour masquer les autres bits de code masqué.
Vous forcera tous les gars qui lit votre code modèle de l'expertise, juste pour éviter de faire votre travail de définition des objets clairs de la sémantique.

Si vous avez décidé d'utiliser les données brutes comme les 3 dimensions des tableaux, vient vivre avec elle, ou bien de définir une classe qui donne une certaine compréhensible sens à vos données.

for (auto& k : A)
for (auto& i : k)
for (auto& current_A : i)
    do_something_on_A(current_A);

est seulement compatible avec l'énigmatique définition d'un vecteur de vecteur de vecteur de type int et non explicite de la sémantique.

10voto

FreeNickname Points 1671
<pre><code></code><p>Mise à jour : Je sais, que vous avez demandé pour elle, mais vous serait mieux pas utiliser qui  :)</p></pre>

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