Aqui está um exemplo do mundo real no qual estou trabalhando agora, a partir de sistemas de processamento / controle de sinal:
Suponha que você tenha alguma estrutura que represente os dados que você está coletando:
struct Sample {
time_t time;
double value1;
double value2;
double value3;
};
Agora, suponha que você os coloque em um vetor:
std::vector<Sample> samples;
... fill the vector ...
Agora, suponha que você queira calcular alguma função (digamos a média) de uma das variáveis em um intervalo de amostras e que você queira fatorar esse cálculo médio em uma função. O ponteiro para membro facilita:
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 Editada em 05/08/2016 para uma abordagem mais concisa das funções de modelo
E, é claro, você pode modelá-lo para calcular uma média para qualquer iterador futuro e qualquer tipo de valor que suporte adição além de si e divisão por 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 - O código acima tem implicações de desempenho
Você deve observar, como logo descobri, que o código acima tem algumas implicações sérias de desempenho. O resumo é que, se você está calculando uma estatística resumida em uma série temporal, ou calculando uma FFT, etc., deve armazenar os valores para cada variável contiguamente na memória. Caso contrário, a iteração sobre a série causará uma falta de cache para cada valor recuperado.
Considere o desempenho deste código:
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;
Em muitas arquiteturas, uma instância de Sample
preencherá uma linha de cache. Portanto, em cada iteração do loop, uma amostra será extraída da memória para o cache. Serão usados 4 bytes da linha de cache e o restante jogado fora, e a próxima iteração resultará em outra falha de cache, acesso à memória e assim por diante.
Muito melhor para fazer isso:
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;
Agora, quando o primeiro valor x for carregado da memória, os próximos três também serão carregados no cache (supondo um alinhamento adequado), o que significa que você não precisa de nenhum valor carregado para as próximas três iterações.
O algoritmo acima pode ser melhorado um pouco mais através do uso de instruções SIMD em, por exemplo, arquiteturas SSE2. No entanto, elas funcionam muito melhor se os valores estiverem todos contíguos na memória e você pode usar uma única instrução para carregar quatro amostras juntas (mais nas versões posteriores do SSE).
YMMV - projete suas estruturas de dados para se adequar ao seu algoritmo.