Ось приклад із реального світу, над яким я працюю зараз, з систем обробки / управління сигналами:
Припустимо, у вас є структура, яка представляє дані, які ви збираєте:
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);
EDIT - Наведений вище код має наслідки для продуктивності
Вам слід зазначити, як я незабаром виявив, що наведений вище код має серйозні наслідки для продуктивності. Підсумок полягає в тому, що якщо ви обчислюєте підсумкову статистику за часовим рядом або обчислюєте FFT тощо, то слід зберігати значення для кожної змінної безперервно в пам'яті. В іншому випадку повторення серіалу призведе до пропуску кешу для кожного отриманого значення.
Розглянемо ефективність цього коду:
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 - спроектуйте свої структури даних відповідно до вашого алгоритму.