Вот реальный пример, над которым я сейчас работаю, из систем обработки / управления сигналами:
Предположим, у вас есть структура, которая представляет данные, которые вы собираете:
struct Sample {
time_t time;
double value1;
double value2;
double value3;
};
Теперь предположим, что вы складываете их в вектор:
std::vector<Sample> samples;
... fill the vector ...
Теперь предположим, что вы хотите вычислить некоторую функцию (скажем, среднее) одной из переменных в диапазоне выборок, и вы хотите разделить это среднее значение в функцию. Указатель на член упрощает:
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);
Примечание Отредактировано 2016/08/05 для более краткого подхода к шаблонам
И, конечно, вы можете создать шаблон для вычисления среднего для любого прямого итератора и любого типа значения, который поддерживает сложение с самим собой и деление на 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);
РЕДАКТИРОВАТЬ - приведенный выше код влияет на производительность
Вы должны заметить, как я вскоре обнаружил, что приведенный выше код имеет некоторые серьезные последствия для производительности. Суть в том, что если вы вычисляете сводную статистику по временному ряду или вычисляете БПФ и т. Д., То вы должны хранить значения для каждой переменной непрерывно в памяти. В противном случае итерация по серии вызовет пропадание кэша для каждого полученного значения.
Учитывайте производительность этого кода:
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;
На многих архитектурах один экземпляр Sample
заполнит строку кэша. Таким образом, на каждой итерации цикла один образец будет извлекаться из памяти в кэш. 4 байта из строки кэша будут использованы, а остальные будут выброшены, и следующая итерация приведет к еще одному отсутствию кэша, доступу к памяти и т. Д.
Намного лучше сделать это:
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;
Теперь, когда первое значение x загружено из памяти, следующие три также будут загружены в кэш (при условии подходящего выравнивания), что означает, что вам не нужно загружать какие-либо значения для следующих трех итераций.
Приведенный выше алгоритм может быть несколько улучшен за счет использования инструкций SIMD, например, для архитектур SSE2. Тем не менее, они работают намного лучше, если все значения непрерывны в памяти, и вы можете использовать одну инструкцию для загрузки четырех выборок вместе (больше в более поздних версиях SSE).
YMMV - разработайте структуры данных в соответствии с вашим алгоритмом.