Вот реальный пример, над которым я сейчас работаю, из систем обработки сигналов / управления:
Предположим, у вас есть структура, которая представляет данные, которые вы собираете:
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 - проектируйте свои структуры данных в соответствии с вашим алгоритмом.