Pregunta Manera elegante de iterar en C ++


Digamos que tengo un vector de polígonos, donde cada polígono contiene un vector de puntos. Tengo que iterar sobre todos los puntos de todos los polígonos muchas veces en mi código, termino escribiendo el mismo código una y otra vez:

for(std::vector<Polygon*>::const_iterator polygon = polygons.begin();
                polygon != polygons.end(); polygon++)
{
        for(std::vector<Point>::const_iterator point = (*polygon)->points.begin();
                        point != (*polygon)->points.end(); point++)
        {
                (*point).DoSomething();
        }
}

Realmente creo que es mucho código para dos iteraciones simples, y siento que está obstruyendo el código e interfiriendo con la legibilidad.

Algunas opciones que pensé son:

  • usando #defines, pero sería poco práctico (para usar en otras partes del código). Además, #defines se consideran malvado hoy en día;
  • iterar sobre vector-> tamaño () - no parece la manera más elegante;
  • llamar a un método con un puntero de función, pero en este caso, el código que debería estar dentro del bucle estaría lejos del bucle.

Entonces, ¿cuál sería la forma más limpia y elegante de hacer esto?


7
2018-01-11 21:10


origen


Respuestas:


En C ++ 11, usando ranged-base para bucles y el auto palabra clave:

for(const auto& polygon : polygons) {
    for(const auto& point : polygon->points) {
        point.DoSomething();
    }
}

24
2018-01-11 21:14



Si no puede usar C ++ 11, boost tiene un PARA CADA macro que genera una gran cantidad de código, pero simplifica drásticamente su código:

BOOST_FOREACH(Polygon * polygon, polygons)
{
    BOOST_FOREACH( Point & point, polygon->points )
    {
        point.doSomething();
    }
}

9
2018-01-11 21:18



Si no puede usar C ++ 11, tal vez escriba def el tipo de iterador a algo más corto como

typedef std::vector<Polygon*>::const_iterator PolyCit;
for (PolyCit polygon = polygons.begin(); polygon != polygons.end(); polygon++)

5
2018-01-11 21:17



El ciclo interno se puede reescribir usando algoritmos como este:

std::for_each(
    (*polygon)->points.begin(), (*polygon)->points.end(), 
    &Point::DoSomething
);

Mezclar esto con el bucle externo es un poco más complejo:

std::for_each(
    polygons.begin(), polygons.end(),
    []( Polygon* polygon ) {
        std::for_each(
            polygon->points.begin(), polygon->points.end(), 
            &Point::DoSomething
        );
    }
);

Si tuviéramos algún tipo de iterador compuesto, podríamos expresar su intención, que es hacer alguna cosa para cada apunta en cada polígono. Y un distancia biblioteca como Boost.Range le permitiría evitar nombrar cada envase dos veces, dado que desea trabajar con todo distancia.

Mi versión ideal de tu código se vería así:

for_each( flatten( polygons ), &Point::DoSomething );

fueron flatten devolvería un ver de cada Point en cada Polygon como si fuera un solo continuo distancia. Tenga en cuenta que esto es algo que se puede lograr de forma clara C ++ 03, y todo lo que nos falta de Boost.Range para lograrlo es una rango de flatenning, que no debería ser difícil de implementar en términos de rango join.

De lo contrario, el basado en rango for-loop Juntos con auto le ayudará a reducir el estándar de iteración lanzar un rango y olvidarse de los tipos complicados.


5
2018-01-11 21:17



Necesitas una capa de abstracción. En lugar de tratar con un vector de polígonos, escribe una clase que administre ese vector. La clase proporciona pares de iteradores para iterar sobre los puntos. El código en el iterador conoce y encapsula esos detalles.


1
2018-01-11 21:28



No importa, esto no funcionará para ti, ya que tienes un vector de punteros en el nivel superior, pero lo seguiré, porque creo que es genial.


La metaprogramación de mi plantilla está un poco oxidada, por lo que podría haber una forma más sencilla de hacerlo, pero:

template<typename C, typename F, size_t depth>
struct nested_for_each_helper
{
    static void do_it(C& c, F& f)
    {
        for (auto& i : c)
            nested_for_each_helper<decltype(i),F,depth-1>::do_it(i,f);
    }
};

template<typename C, typename F>
struct nested_for_each_helper<C,F,0>
{
    static void do_it(C& c, F& f)
    {
        f(c);
    }
};

template<size_t depth, typename C, typename F>
void nested_for_each(C& c, F& f)
{
    nested_for_each_helper<C,F,depth>::do_it(c,f);
}

int main()
{        
    int n[3][3][3][3];
    int i = 0;
    nested_for_each<4>(n,[&i](int& n) { n = i++; });
    nested_for_each<4>(n,[](int n){
        std::cout << n << ' ';
    });
}

Para su caso, puede usarlo así (no, no puede):

nested_for_each<2>(polygons, [](Point const& p) { p.DoSomething(); });

1
2018-01-11 21:28



En primer lugar, está el viejo bucle simple basado en enteros. Un poco más corto que los iteradores.

for( int i = 0 ; i < polygons.size() ; i++ )
{
    for( int j = 0 ; j < polygons[i]->points.size(); j++)
    {
        Point* p = polygons[i]->points[j] ;
        p->DoSomething();
    }
}

Si no te gusta eso, y no tienes C ++ 11 disponible, puedes escribir una función que acepte un funtor (estos estaban en C ++ 0x bajo std::tr1 Creo):

void eachPoint( function<void (Point* p)> func )
{
    for( int i = 0 ; i < polygons.size() ; i++ )
    {
        for( int j = 0 ; j < polygons[i]->points.size(); j++)
        {
            Point* p = polygons[i]->points[j] ;
            func(p);
        }
    }
}

Alternativamente, una vieja macro simple:

#define EACH_POLYGON( polyCollection ) for( int _polyI = 0 ; _polyI < polyCollection.size() ; _polyI++ ) \
for( int _ptNo = 0, Point* p=polyCollection[_polyI]->points[0] ; j < polyCollection[_polyI]->points.size() && (p=polyCollection[_polyI]->points[_ptNo]); _ptNo++)

EACH_POLYGON( polyCollection )
{
    p->DoSomething();
}

0
2018-01-11 21:37