【发布时间】:2019-08-13 18:13:12
【问题描述】:
我在网上阅读了有关 C++ 的内容并看到了这样的说法:
谓词不应因函数调用而修改其状态。
我不明白这里的“状态”是什么意思。有人可以举个例子吗?
【问题讨论】:
-
传递给函数的谓词对象可能被复制任意次数(例如递归快速排序)。如果它们的比较操作(函数调用)改变了它们的状态,每个谓词都会得到不同的状态。
我在网上阅读了有关 C++ 的内容并看到了这样的说法:
谓词不应因函数调用而修改其状态。
我不明白这里的“状态”是什么意思。有人可以举个例子吗?
【问题讨论】:
让我们以算法std::count_if 为例。它遍历一个范围并计算给定谓词评估为真的频率。进一步假设我们要检查容器中有多少元素小于给定数字,例如5 或 15。
谓词可以是很多东西。它必须是可调用的。它可以是函子:
struct check_if_smaller {
int x;
bool operator()(int y) const { 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()),这不应该改变。
来自wikipedia:
在数理逻辑中,谓词通常被理解为 布尔值函数 P: X→ {true, false},称为谓词 十、然而,谓词有许多不同的用途和解释 数学和逻辑,以及它们的精确定义、意义和用途 因理论而异。
粗略地说,谓词是将某物映射到布尔值的函数。我们使用的函子不仅仅是一个函数,而是一个具有状态的函数对象这一事实可以被视为实现细节,并且对于相同的输入重复评估相同的谓词通常会产生相同的结果。此外,算法会做出这种假设,并且没有什么能真正阻止它们复制您传递的谓词(实际上是the standard explicitly permits them to do so)。如果评估谓词会改变其内部状态,则算法可能无法按预期工作。
【讨论】:
a 模拟谓词“检查一个数字是否小于 5”。如果您更改其 x 成员,则它是一个不同的谓词。当然,您可以将 x 设为私有,但我不明白这有什么不同或有什么优势
const,如果它不修改它的状态:bool operator()(int y) const { return y < x; }。
用外行的话来说,谓词中的一个状态是一个数据成员。更改状态的谓词意味着成员在算法执行期间发生了变化,并且该更改将影响谓词行为。
避免这种情况的原因是算法没有义务保留谓词的单个实例。它们可能很容易被复制,并且在一个副本中更改了状态,不会与另一个副本中的状态共享。结果,程序将出现意外行为(对于期望状态更改生效的人)。
【讨论】:
基本上什么标准说谓词应该像纯函数一样(在数学术语中),即它的返回值应该仅取决于输入。
之所以提到状态,是因为谓词可以被复制或可以在不同的线程中调用,这取决于实现和平台行为。对于非函数的 lambda 和其他可调用对象,这可能意味着对存储的无序访问、通过引用捕获或访问不同的值(如果它们是按值捕获的)。对于一个函数来说,这意味着任何副作用(包括静态变量的变化)都可能导致问题。
如果排序谓词对同一对返回不同的结果,则某些排序算法将无效。
【讨论】:
除了其他答案之外,许多采用谓词的算法不承诺任何特定的遍历顺序(ExecutionPolicy 重载允许交错遍历)。对于同一个问题,您可能会得到不同的答案。
如果有多个线程调用1谓词并且它改变了一些共享值,那就是a data race,即未定义的行为。
【讨论】: