Voici un exemple concret sur lequel je travaille en ce moment, à partir des systèmes de traitement / contrôle du signal:
Supposons que vous ayez une structure qui représente les données que vous collectez:
struct Sample {
time_t time;
double value1;
double value2;
double value3;
};
Supposons maintenant que vous les bourriez dans un vecteur:
std::vector<Sample> samples;
... fill the vector ...
Supposons maintenant que vous vouliez calculer une fonction (disons la moyenne) d'une des variables sur une plage d'échantillons, et que vous souhaitiez factoriser ce calcul moyen dans une fonction. Le pointeur sur membre simplifie:
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);
Note modifiée le 08/08/2016 pour une approche modèle-fonction plus concise
Et, bien sûr, vous pouvez le modéliser pour calculer une moyenne pour tout itérateur direct et tout type de valeur qui prend en charge l'addition avec lui-même et la division par 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 - Le code ci-dessus a des implications sur les performances
Vous devriez noter, comme je l'ai rapidement découvert, que le code ci-dessus a de sérieuses implications en termes de performances. Le résumé est que si vous calculez une statistique récapitulative sur une série temporelle ou calculez une FFT, etc., vous devez stocker les valeurs de chaque variable de manière contiguë en mémoire. Sinon, l'itération sur la série entraînera un échec de cache pour chaque valeur récupérée.
Considérez les performances de ce code:
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;
Sur de nombreuses architectures, une instance de Sample
remplira une ligne de cache. Ainsi, à chaque itération de la boucle, un échantillon sera extrait de la mémoire dans le cache. 4 octets de la ligne de cache seront utilisés et le reste jeté, et la prochaine itération entraînera un autre échec de cache, un accès à la mémoire, etc.
Il vaut mieux faire cela:
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;
Maintenant, lorsque la première valeur x est chargée à partir de la mémoire, les trois suivantes seront également chargées dans le cache (en supposant un alignement approprié), ce qui signifie que vous n'avez pas besoin de valeurs chargées pour les trois prochaines itérations.
L'algorithme ci-dessus peut être amélioré un peu plus en utilisant des instructions SIMD, par exemple sur les architectures SSE2. Cependant, ceux-ci fonctionnent beaucoup mieux si les valeurs sont toutes contiguës en mémoire et que vous pouvez utiliser une seule instruction pour charger quatre échantillons ensemble (davantage dans les versions SSE ultérieures).
YMMV - concevez vos structures de données en fonction de votre algorithme.