Я читал в Интернете о C++ и наткнулся на это утверждение:
Предикаты не должны изменять свое состояние из-за вызова функции.
Я не понял, что здесь означает "государство". Может кто-нибудь уточнить, пожалуйста, с примером?
Я читал в Интернете о C++ и наткнулся на это утверждение:
Предикаты не должны изменять свое состояние из-за вызова функции.
Я не понял, что здесь означает "государство". Может кто-нибудь уточнить, пожалуйста, с примером?
Давайте рассмотрим алгоритм std::count_if
в качестве примера. Он пересекает диапазон и считает, как часто данный предикат оценивается как истинный. Далее предположим, что мы хотим проверить, сколько элементов в контейнере меньше заданного числа, например, 5 или 15.
Предикатом может быть много вещей. Это просто должно быть вызвано. Это может быть функтор:
struct check_if_smaller {
int x;
bool operator()(int y) { return y < x; }
};
Вы можете создавать разные экземпляры этого предиката, например, эти два
check_if_smaller a{5};
check_if_smaller b{15};
можно использовать для проверки того, что числа меньше, чем 5
или 15
соответственно:
bool test1 = a(3); // true because 3 < 5
bool test2 = b(20); // false because 20 is not < 15
Элемент x
является состоянием предиката. Обычно это не должно меняться при применении предиката (вызывая его operator()
).
Из Википедии:
В математической логике предикат обычно понимается как Булевозначная функция P: X → {true, false}, называемая предикатом на X. Однако предикаты имеют много разных применений и интерпретаций в математика и логика и их точное определение, значение и использование будет варьироваться от теории к теории.
Небрежно говоря, предикат - это функция, отображающая что-то в логическое значение. Тот факт, что мы используем функтор, который является не просто функцией, а функциональным объектом с состоянием, может рассматриваться как деталь реализации, и повторная оценка одного и того же предиката для одного и того же ввода обычно дает тот же результат. Кроме того, алгоритмы делают это предположение, и ничто действительно не мешает им копировать предикат, который вы передаете. Если оценка предиката изменит его внутреннее состояние, алгоритм может работать не так, как ожидалось.
С точки зрения непрофессионалов, состояние в предикате является членом данных. Изменение состояния предикатом означает, что член изменяется во время выполнения алгоритма, и это изменение повлияет на поведение предиката.
Причиной этого является тот факт, что алгоритмы не обязаны хранить единственный экземпляр предиката. Их можно легко скопировать, а состояние, измененное в одной копии, не будет передано состоянию в другой копии. В результате программа будет вести себя неожиданно (для того, кто ожидает изменения состояния).
По существу, то, что стандарт говорит, что предикат должен действовать как чистая функция (в математических терминах), то есть его возвращаемое значение должно зависеть только от ввода.
Упоминание о состоянии, потому что предикаты могут быть скопированы или могут быть вызваны в разных потоках, что зависит от реализации и поведения платформы. Для лямбды и других вызываемых объектов, которые не являются функциями, это может означать неупорядоченный доступ к хранилищу, захваченный по ссылке, или доступ к другим значениям, если они были захвачены по значению. Для функции это означает, что любые побочные эффекты (включая изменение статических переменных) могут привести к проблемам.
Если предикат для сортировки возвращает разные результаты для одной и той же пары, то некоторые алгоритмы сортировки становятся недействительными.
В дополнение к другим ответам, многие из алгоритмов, которые принимают предикаты, не обещают какого-либо определенного порядка обхода (перегрузки ExecutionPolicy допускают чередующийся обход). Вы можете получить разные ответы на один и тот же вопрос.
Если есть несколько потоков, вызывающих 1 предикат, и он меняет некое общее значение, то это гонка данных, то есть неопределенное поведение.