aumentare il problema lambda o fenice: usare std :: for_each per operare su ogni elemento di un contenitore
-
03-07-2019 - |
Domanda
Ho riscontrato un problema durante la pulizia di un vecchio codice. Questa è la funzione:
uint32_t ADT::get_connectivity_data( std::vector< std::vector<uint8_t> > &output )
{
output.resize(chunks.size());
for(chunk_vec_t::iterator it = chunks.begin(); it < chunks.end(); ++it)
{
uint32_t success = (*it)->get_connectivity_data(output[it-chunks.begin()]);
}
return TRUE;
}
Quello che mi interessa fare è ripulire il ciclo for in modo che sia un'espressione lambda ma mi sono rapidamente bloccato su come avrei passato esattamente l'argomento corretto a get_connectivity_data. get_connectivity_data prende uno std :: vector per riferimento e lo riempie di alcuni dati. l'output contiene uno std :: vector per ogni "pezzo".
Fondamentalmente la mia conclusione è stata che è stato sostanzialmente più facile, più pulito e più breve lasciare il mio codice così com'è.
EDIT:
Quindi la risposta più vicina alla mia domanda come immaginavo sarebbe stata questa:
std::for_each( chunks.begin(), chunks.end(),
bind( &chunk_vec_t::value::type::get_connectivity_data,
_1,
output[ std::distance( _1, chunks.begn() ]
)
);
Tuttavia quel codice non viene compilato, ho apportato alcune modifiche al codice per farlo compilare ma ho riscontrato 2 problemi:
- _ 1 è un ptr intelligente e std :: distance non ha funzionato su di esso, penso che avrei dovuto usare & amp; chunks [0] come inizio
- Poiché _ 1 è un puntatore intelligente, ho dovuto fare: & amp; chunk_vec_t :: value_ type :: ValueType :: get_ connectivity_ data che ha causato un arresto anomalo nel compilatore VC9 ...
La risposta relativa agli zip_ iteratori è stata buona fino a quando non ho letto di più e scoperto che per questo particolare utilizzo, la quantità di codice aggiuntivo necessaria era sostanziale (vincolante questo e quello, ecc.)
EDIT2:
Ho trovato una soluzione accettabile che è sia bassa sulla sintassi estranea che chiara, che ho pubblicato qui e sotto.
std::transform(chunks.begin(), chunks.end(), back_inserter(tmp), boost::bind(&ADTChunk::get_connectivity_data, _1) );
Soluzione
Dopo un po 'di lavoro ho trovato questa soluzione:
std::transform(chunks.begin(), chunks.end(), back_inserter(tmp), boost::bind(&ADTChunk::get_connectivity_data, _1) );
Mi ha richiesto di cambiare get_connectivity_data per restituire std :: vector invece di prenderne uno come riferimento, e mi ha anche richiesto di cambiare gli elementi dei blocchi in modo che siano boost :: shared_ptr invece di Loki :: SmartPtr.
Altri suggerimenti
Penso che tu abbia ragione nel pensare che la cosa migliore da fare è lasciare il codice così com'è. Se hai difficoltà a capirlo quando (a) lo stai scrivendo e (b) capisci il problema esatto che stai cercando di risolvere, immagina quanto sarà difficile quando qualcuno arriverà tra 3 anni e deve comprendere sia il problema che la soluzione che hai scritto.
Senza vedere il codice per l'intera classe, è difficile determinare cosa funzionerà. Personalmente, penso che BOOST_FOREACH sia più pulito in questo caso, ma a scopo di riferimento, potrei provare a fare qualcosa del genere usando lambdas (nota che non posso testare la compilazione)
uint32_t ADT::get_connectivity_data( std::vector< std::vector<uint8_t> > &output )
{
using namespace boost::lambda;
output.resize( chunks.size() );
std::for_each( chunks.begin(), chunks.end(),
bind( &chunk_vec_t::value::type::get_connectivity_data,
_1,
output[ std::distance( _1, chunks.begn() ]
)
);
return TRUE;
}
In realtà non capisco cosa stai guidando per quanto riguarda la lambda, ma posso dare un paio di suggerimenti generali per la pulizia del codice che coinvolgono i contenitori STL.
Usa i typedef di tutti i tipi di container STL:
typedef std::vector<uint8_t> Chunks;
typedef std::vector<Chunks> Output;
uint32_t ADT::get_connectivity_data( Output &output )
Dato che stai parlando di usare Boost, usa Boost. foreach :
BOOST_FOREACH(chunk_vec_t::value_type &chunk, chunks)
uint32_t success =
chunk->get_connectivity_data(output[std::distance(&chunk, chunks.begin())]);
Pugnala al buio su "lambda" cosa:
typedef const boost::function2<uint32_t, chunk_vec_t::value_type, Chunks>
GetConnectivity;
uint32_t ADT::get_connectivity_data(Output &output, GetConnectivity &getConnectivity)
{
output.resize(chunks.size());
BOOST_FOREACH(chunk_vec_t::value_type &chunk, chunks)
uint32_t success =
getConnectivity(chunk, output[std::distance(&chunk, chunks.begin())]);
return TRUE;
}
Quindi potresti chiamarlo come:
get_connectivity_data(output,
boost::bind(&chunk_vec_t::value_type::get_connectivity_data, _1, _2));
Quello che stai effettivamente facendo è eseguire un'operazione su due container in parallelo. Questo è ciò per cui è progettato boost :: zip_iterator.
Tuttavia, l'unico motivo necessario per elaborare i contenitori in parallelo è che Chunk :: get_connectivity_data accetta un parametro out. Se dovesse restituire per valore (utilizzando le eccezioni per segnalare errori), potresti semplicemente utilizzare un iteratore di inserimento.
Per qualche ragione, i principianti di STL insistono sempre nell'utilizzare un operatore vector :: iterator anziché l'operatore più leggibile (e a tempo costante) []. L'espressione it-chunks.begin ()
avrebbe dovuto dire all'autore originale che aveva già perso il gioco del programmatore di smartass e che aveva bisogno di un umile indice dopo tutto:
for (size_t i = 0, size = chunks.size(); i < size; ++i)
{
chunks[i]->get_connectivity_data(output[i]);
}
OP potrebbe anche considerare di perdere il codice di ritorno spurio e renderlo una funzione membro const.