📄 item_087.htm
字号:
</o:shapelayout></xml><![endif]-->
</head>
<body lang=ZH-CN link=blue vlink=purple style='tab-interval:36.0pt'>
<div class=Section1>
<h3><span lang=EN-US>87. </span><span style='font-family:宋体;mso-ascii-font-family:
Arial;mso-hansi-font-family:Arial'>使</span><span lang=EN-US>predicate</span><span
style='font-family:宋体;mso-ascii-font-family:Arial;mso-hansi-font-family:Arial'>成为纯函数(</span><span
lang=EN-US>pure function</span><span style='font-family:宋体;mso-ascii-font-family:
Arial;mso-hansi-font-family:Arial'>)。</span></h3>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><b style='mso-bidi-font-weight:normal'><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>摘要</span></b></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><span lang=EN-US>Predicate purity</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>:</span><span lang=EN-US> Predicate</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>是一个返回是</span><span lang=EN-US>/</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>否的函数对象,典型的情况是作为一个布尔类型。从数学的角度来说,如果一个函数只依赖于它的参数,那么该函数是纯的(注意这里的“纯”与纯虚函数无关)。</span></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>不要让</span><span lang=EN-US>predicate</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>持有或存取那些会影响其</span><span lang=EN-US>operator()</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>的结果的状态,这包括成员和全局状态。对</span><span lang=EN-US>predicate</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>来说,最好是使</span><span lang=EN-US>operator()</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>成为一个</span><span lang=EN-US>const</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>成员函数(参见第</span><span lang=EN-US>15</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>条)。</span></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><b style='mso-bidi-font-weight:normal'><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>讨论</span><a name=ch75index09></a><a name=ch75index08></a><a
name=ch75lev1sec2></a><span lang=EN-US><o:p></o:p></span></b></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>算法会在未知的时间,以未知的次序,产生未知数量的</span><span
lang=EN-US>predicate</span><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>复本,然后把这些复本传给其它函数,同时很自然地认为这些复本都是等价的。</span></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>要确保</span><span lang=EN-US>predicate</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>的复本是真正等价的责任在你,以上就是原因,这意味着它们必须是纯函数:除了传给</span><span
lang=EN-US>operator()</span><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>的参数之外,函数的结果不受任何其它因素的影响。此外,在对相同的参数集求值时,</span><span
lang=EN-US>predicate</span><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>还必须始终返回相同的结果。</span></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>带状态的</span><span lang=EN-US>predicate</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>可能看起来有用,但是在和</span><span lang=EN-US>C++</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>标准库及标准算法一起使用时,它们显然不太有用,这是故意的。特别是,仅在下面的条件成立时,带状态的</span><span
lang=EN-US>predicate</span><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>才会有用:</span></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<ul style='margin-top:0cm' type=disc>
<li class=MsoNormal style='mso-list:l1 level1 lfo5;tab-stops:list 36.0pt'><span
lang=EN-US>predicate</span><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>不会被复制:标准算法并没有对此做出任何保证,而事实上是它们认为</span><span
lang=EN-US>predicate</span><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>可以被安全地复制。</span></li>
</ul>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<ul style='margin-top:0cm' type=disc>
<li class=MsoNormal style='mso-list:l1 level1 lfo5;tab-stops:list 36.0pt'><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>在应用</span><span lang=EN-US>predicate</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>时有确定的顺序,并在文档中对该顺序做了说明:对以何种次序将</span><span lang=EN-US>predicate</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>应用于区间内的元素,标准算法没有做出任何保证。如果不能保证存取对象的次序,那么像“标记第三个元素”(参见示例)之类的操作就没有什么意义,因为“第三次”会存取哪个元素没有一个明确的定义。</span></li>
</ul>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>要绕开第一点是可能的,可以编写一个轻量级的</span><span
lang=EN-US>predicate</span><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>,在其内部使用引用计数来共享深层状态。这同时解决了</span><span
lang=EN-US>predicate</span><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>的复制问题,因为这样的</span><span
lang=EN-US>predicate</span><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>可以被安全地复制而不会在应用于对象时改变语义。(参见</span><span
lang=EN-US>[Sutter02]</span><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>。)但是,要绕开第二点是不可能的。</span></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>应该始终把</span><span lang=EN-US>predicate</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>类型的</span><span lang=EN-US>operator()</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>声明为</span><span lang=EN-US>const</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>成员函数,这样的话在你试图改变</span><span lang=EN-US>predicate</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>可能包含的数据成员时,编译器会产生错误,这就有助于避免此类疏忽。虽然这无法防止所有的滥用——例如,它无法识别对全局数据的存取——但它至少可以让编译器帮助你避免最常见的疏忽。</span></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><b style='mso-bidi-font-weight:normal'><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>示例</span></b></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><i style='mso-bidi-font-style:normal'><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>示例:</span><span lang=EN-US>FlagNth</span></i><i
style='mso-bidi-font-style:normal'><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>。</span></i><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>这个经典的例子来自</span><span lang=EN-US>[Sutter02]</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>,它的目的是从容器</span><span lang=EN-US>v</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>中移除第三个元素。</span></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal style='margin-left:36.0pt'><span lang=EN-US>class FlagNth {</span></p>
<p class=MsoNormal style='margin-left:36.0pt'><span lang=EN-US>public:</span></p>
<p class=MsoNormal style='margin-left:36.0pt'><span lang=EN-US><span
style='mso-spacerun:yes'> </span>FlagNth( size_t n ) : current_(0), n_(n)
{}</span></p>
<p class=MsoNormal style='margin-left:36.0pt'><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal style='margin-left:36.0pt'><span lang=EN-US><span
style='mso-spacerun:yes'> </span><i style='mso-bidi-font-style:normal'>//
</i></span><i style='mso-bidi-font-style:normal'><span style='font-family:宋体;
mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:"Times New Roman"'>当且仅当第</span><span
lang=EN-US>n</span></i><i style='mso-bidi-font-style:normal'><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>次调用时返回</span><span lang=EN-US>true<o:p></o:p></span></i></p>
<p class=MsoNormal style='margin-left:36.0pt'><span lang=EN-US><span
style='mso-spacerun:yes'> </span>template<typename T></span></p>
<p class=MsoNormal style='margin-left:36.0pt'><span lang=EN-US><span
style='mso-spacerun:yes'> </span>bool operator()( const T& ) {return
++current_ == n_; } <i style='mso-bidi-font-style:normal'>// </i></span><i
style='mso-bidi-font-style:normal'><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>糟糕:不是</span><span
lang=EN-US>const<o:p></o:p></span></i></p>
<p class=MsoNormal style='margin-left:36.0pt'><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal style='margin-left:36.0pt'><span lang=EN-US>private:</span></p>
<p class=MsoNormal style='margin-left:36.0pt'><span lang=EN-US><span
style='mso-spacerun:yes'> </span>size_t current_, n_;</span></p>
<p class=MsoNormal style='margin-left:36.0pt'><span lang=EN-US>};</span></p>
<p class=MsoNormal style='margin-left:36.0pt'><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal style='margin-left:36.0pt'><i style='mso-bidi-font-style:
normal'><span lang=EN-US>// … </span></i><i style='mso-bidi-font-style:normal'><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>稍后</span><span lang=EN-US> …<o:p></o:p></span></i></p>
<p class=MsoNormal style='margin-left:36.0pt'><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal style='margin-left:36.0pt'><span lang=EN-US>v.erase(
remove_if( v.begin(), v.end(), FlagNth(3) ) );</span></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>无法保证这样可以移除第三个元素,即便它是为此目的而设计的。在大多数实际的</span><span
lang=EN-US>STL</span><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>实现中,它会同时移除第三个和第六个元素。为什么?因为一般来说</span><span
lang=EN-US>remove_if</span><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>是基于</span><span
lang=EN-US>find_if</span><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>和</span><span
lang=EN-US>remove_copy_if</span><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>来实现的,它会把</span><span
lang=EN-US>predicate</span><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>的复本传给这些函数,而在这之前它自己可能已经执行了某些会影响到</span><span
lang=EN-US>predicate</span><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>状态的操作。</span></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>从概念上说,这个例子有悖常理,因为</span><span
lang=EN-US>remove_if</span><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>仅保证会移除满足条件的所有元素。文档中并没有说明它会以何种顺序存取或移除区间内的元素,因此上面的代码依赖于臆想,这样的臆想只是假设,既没有文档支持,也无法令人满意。</span></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>移除某个特定元素的正确方法是迭代至该元素,然后调用</span><span
lang=EN-US>erase</span><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>。</span></p>
</div>
</body>
</html>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -