Почему предикаты алгоритмов последовательности выполняются копией?
Мне интересно, почему функторы передаются копией в функции algorithm
:
template <typename T> struct summatory
{
summatory() : result(T()) {}
void operator()(const T& value)
{ result += value; std::cout << value << "; ";};
T result;
};
std::array<int, 10> a {{ 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 }};
summatory<int> sum;
std::cout << "\nThe summation of: ";
std::for_each(a.begin(), a.end(), sum);
std::cout << "is: " << sum.result;
Я ожидал следующий вывод:
Суммирование: 1; 1; 2; 3; 5; 8; 13; 21; 34; 55; is: 143
Но sum.result
содержит 0
, то есть значение по умолчанию, назначенное в ctor. Единственный способ добиться желаемого поведения - захватить возвращаемое значение for_each
:
sum = std::for_each(a.begin(), a.end(), sum);
std::cout << "is: " << sum.result;
Это происходит потому, что функтор передается с помощью for_each
вместо ссылки:
template< class InputIt, class UnaryFunction >
UnaryFunction for_each( InputIt first, InputIt last, UnaryFunction f );
Таким образом, внешний функтор остается нетронутым, а внутренний (который является копией внешнего) обновляется и возвращается после выполнения алгоритма (живая демонстрация), поэтому результат будет скопирован (или перемещен) снова после выполнения всех операций.
Должна быть веская причина, чтобы сделать эту работу таким образом, но я действительно не понимаю обоснования в этом дизайне, поэтому мои вопросы:
- Почему предикаты алгоритмов последовательности-операций передаются копией вместо ссылки?
- Какие преимущества предлагает метод сквозной копии перед обратным ссылкой?
Ответы
Ответ 1
Это в основном по историческим причинам. В 98 году, когда весь материал альго, попавший в стандартные ссылки, имел все проблемы. Это было окончательно разрешено с помощью основных и библиотечных DRs на С++ 03 и далее. Также разумные ref-wrappers и фактически работающие связывание поступают только в TR1.
Те, кто пытался использовать algos с ранним С++ 98, имеющим функции с использованием ref params или return, могут вспомнить всевозможные проблемы. Самозанятые альго также были склонны поражать страшную проблему "ссылки на ссылку".
Передача по значению, по крайней мере, работала нормально, и вряд ли создала много проблем - и на начальном этапе у него были рефлексы и кремы, чтобы помочь вам в том, где вам нужно было настроить.
Ответ 2
Это чисто догадка, но...
... позволяет на мгновение предположить, что он принимает по ссылке на const. Это означает, что все ваши члены должны быть изменчивыми, а оператор должен быть const. Это просто не кажется "правильным".
... позволяет на мгновение предположить, что он берет ссылку на не-const. Он будет называть неконстантный оператор, члены могут просто работать над штрафом. Но что, если вы хотите передать ad-hoc-объект? Как результат операции привязки (даже С++ 98 имел - уродливые и простые - инструменты привязки)? Или сам тип просто делает все, что вам нужно, и после этого вам не нужен объект, и вы просто хотите назвать его как for_each(b,e,my_functor());
? Это не будет работать, поскольку временные пользователи не могут связываться с неконстантными ссылками.
Так что, может быть, это не самый лучший, но наименее плохая опция здесь, чтобы взять по стоимости, скопировать ее в процессе столько, сколько необходимо (надеюсь, не слишком часто), а затем, когда это будет сделано, верните ее из for_each. Это отлично работает с довольно низкой сложностью вашего суммирующего объекта, не требует добавления изменчивого материала, такого как метод ссылки на константу, и работает также со временными.
Но YMMV, и, скорее всего, были членами комитета, и я бы догадался, что это было в конечном итоге голосование по тому, что, по их мнению, наиболее вероятно подходит для большинства случаев использования.
Ответ 3
Возможно, это может быть обходным путем. Захватите функтор как ссылку и назовите его в лямбда
std::for_each(a.begin(), a.end(), [&sum] (T& value)
{
sum(value);
});
std::cout << "is: " << sum.result;