Замена необработанных циклов абстрагированными алгоритмами - это хороший стиль, потому что тогда вы можете многократно использовать алгоритм, но протестировать его только один раз. Обертывание цикла таким способом может показаться синтаксическим сахаром, но это значительно снижает вероятность ошибок в вашем коде, потому что теперь вы можете выполнять обширные модульные тесты по абстрагированному алгоритму, и вам никогда не придется беспокоиться о неправильной реализации его, когда вам это нужно.
Однако вы сравниваете яблоки и апельсины здесь. Ваша реализация max_element
всегда вычисляет Score()
для ее сравнения, тогда как ваш цикл for
кэширует результат функции Score()
.
Лучшая реализация Node
может быть:
class Node {
mutable:
double cached_score = std::numeric_limits<double>::quiet_Nan();
public:
auto Score() const -> double {
if(std::isnan(cached_score)){
std::cout << "complex calculation\n";
counter++;
cached_score = 1;
}
return cached_score;
}
void invalidate_cache() {
cached_score = std::numeric_limits<double>::quiet_Nan();
}
};
Таким образом, сложное вычисление выполняется только один раз.
В качестве альтернативы напишите свою собственную абстракцию:
#include <cfloat>
#include <iostream>
#include <array>
#include <algorithm>
#include <numeric>
static int counter;
class Node {
public:
auto Score() const -> double {
std::cout << "complex calculation\n";
counter++;
return 1;
}
};
template<class ForwardIt, class Evaluate, class Compare>
ForwardIt max_eval_element(
ForwardIt first,
ForwardIt last,
Evaluate eval,
Compare comp
){
if (first == last) return last;
ForwardIt largest = first;
auto largest_val = eval(*first);
++first;
for (; first != last; ++first) {
const auto this_val = eval(*first);
if (comp(largest_val, this_val)) {
largest = first;
largest_val = this_val;
}
}
return largest;
}
int main()
{
std::array<Node, 10> nodes;
counter = 0;
Node const* nodePtr = max_eval_element(std::cbegin(nodes), std::cend(nodes),
[](Node const& node){ return node.Score(); },
[](double const &a, double const &b) {
return a<b;
});
std::cout << "algorithm count " << counter << std::endl;
counter = 0;
double maxScore = -FLT_MAX;
for (const auto& node : nodes) {
auto score = node.Score();
if (score > maxScore) {
maxScore = score;
nodePtr = &node;
}
}
std::cout << "raw loop count " << counter << std::endl;
}
В этом случае оба цикла выполняют одинаковое количество вычислений.
Многие внутренние кодовые базы, с которыми я работал, имеют обширные библиотеки, расширяющие STL. Это дает командам, над которыми я работал, большую уверенность в том, что их код написан правильно, и позволяет вам с первого взгляда интерпретировать сложные операции. Таким образом, эти абстракции также сокращают усилия по пониманию кода и общению.