Ecco un esempio reale su cui sto lavorando in questo momento, dai sistemi di elaborazione / controllo del segnale:
Supponiamo di avere una struttura che rappresenti i dati che stai raccogliendo:
struct Sample {
time_t time;
double value1;
double value2;
double value3;
};
Supponiamo ora di inserirli in un vettore:
std::vector<Sample> samples;
... fill the vector ...
Supponiamo ora di voler calcolare alcune funzioni (ad esempio la media) di una delle variabili su un intervallo di campioni e di voler fattorizzare questo calcolo medio in una funzione. Il puntatore al membro semplifica:
double Mean(std::vector<Sample>::const_iterator begin,
std::vector<Sample>::const_iterator end,
double Sample::* var)
{
float mean = 0;
int samples = 0;
for(; begin != end; begin++) {
const Sample& s = *begin;
mean += s.*var;
samples++;
}
mean /= samples;
return mean;
}
...
double mean = Mean(samples.begin(), samples.end(), &Sample::value2);
Nota Modificato il 2016/08/05 per un approccio più conciso alla funzione modello
E, naturalmente, puoi modellarlo per calcolare una media per qualsiasi forward-iterator e qualsiasi tipo di valore che supporti l'aggiunta con se stesso e la divisione per size_t:
template<typename Titer, typename S>
S mean(Titer begin, const Titer& end, S std::iterator_traits<Titer>::value_type::* var) {
using T = typename std::iterator_traits<Titer>::value_type;
S sum = 0;
size_t samples = 0;
for( ; begin != end ; ++begin ) {
const T& s = *begin;
sum += s.*var;
samples++;
}
return sum / samples;
}
struct Sample {
double x;
}
std::vector<Sample> samples { {1.0}, {2.0}, {3.0} };
double m = mean(samples.begin(), samples.end(), &Sample::x);
EDIT - Il codice sopra ha implicazioni sulle prestazioni
Dovresti notare, come ho presto scoperto, che il codice sopra ha alcune gravi implicazioni sulle prestazioni. Il riepilogo è che se stai calcolando una statistica riassuntiva su una serie temporale, o calcolando una FFT ecc., Allora dovresti memorizzare i valori per ogni variabile contigui nella memoria. Altrimenti, l'iterazione sulla serie provocherà un errore nella cache per ogni valore recuperato.
Considera le prestazioni di questo codice:
struct Sample {
float w, x, y, z;
};
std::vector<Sample> series = ...;
float sum = 0;
int samples = 0;
for(auto it = series.begin(); it != series.end(); it++) {
sum += *it.x;
samples++;
}
float mean = sum / samples;
Su molte architetture, un'istanza di Sample
riempirà una riga della cache. Quindi ad ogni iterazione del ciclo, un campione verrà estratto dalla memoria nella cache. Verranno utilizzati 4 byte dalla riga della cache e il resto verrà eliminato e la successiva iterazione comporterà un'altra mancata cache, accesso alla memoria e così via.
Molto meglio fare questo:
struct Samples {
std::vector<float> w, x, y, z;
};
Samples series = ...;
float sum = 0;
float samples = 0;
for(auto it = series.x.begin(); it != series.x.end(); it++) {
sum += *it;
samples++;
}
float mean = sum / samples;
Ora, quando il primo valore x viene caricato dalla memoria, anche i successivi tre verranno caricati nella cache (supponendo un adeguato allineamento), il che significa che non è necessario caricare alcun valore per le successive tre iterazioni.
L'algoritmo di cui sopra può essere ulteriormente migliorato mediante l'uso di istruzioni SIMD su ad esempio architetture SSE2. Tuttavia, funzionano molto meglio se i valori sono tutti contigui in memoria e puoi usare una singola istruzione per caricare quattro campioni insieme (più nelle versioni SSE successive).
YMMV: progetta le tue strutture dati in base all'algoritmo.