有状态的仿函数& STL:未定义的行为

时间:2011-05-24 15:37:11

标签: c++ stl functor

我正在关注此Function objects tutorial

下面的复制意大利面:

我无法理解以下内容:

谓词应始终作为无状态函数对象实现,以避免意外结果。无法保证算法在内部复制谓词的频率。因此,具有作为有状态函数对象实现的谓词可能具有未执行的结果。

示例如下:

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>

class predicate
{
public:
   predicate(int condition) :
      condition_(condition), state_(0) {}
   bool operator()(int) { return ++state_ == condition_; }

private:
   int condition_;
   int state_;
};

int main()
{
   std::vector<int> vec;
   vec.push_back(1);
   vec.push_back(2);
   vec.push_back(3);
   vec.push_back(4);
   vec.push_back(5);

   predicate p(2);
   std::vector<int>::iterator pos =
      std::remove_if(vec.begin(), vec.end(), p);
   vec.erase(pos, v.end());

   std::copy(vec.begin(), vec.end(),
             std::ostream_iterator<int>(std::cout, " "));

   return 0;
}

如果我理解(读)它正确,它试图删除向量中标记为2的元素。 remove_if算法返回容器的新端并尝试擦除所有容器。

输出:

1 3 5

显然,不仅第二个元素被删除,第四个元素也被删除。这种好奇心的答案很简单,所使用的算法'remove_if'在执行期间在内部复制谓词。此内部副本创建一个包含其原始状态的新谓词对象。

虽然我可以阅读似乎正在发生的事情,但我无法想象幕后发生的事情,即使是第四个要移动到容器末端的元素。这与单程或多程算法有关吗? (如果有人能指出我如何推断出同样的方法,我将不胜感激)

另一方面,如果我评论擦除&amp;注意输出。

1 3 5 4 5

导致容器损坏的原因是什么?

1 个答案:

答案 0 :(得分:20)

该引用的含义应以面值表示。对于大多数STL算法,您不应该实现谓词仿函数,使其具有可观察状态(AKA“副作用”),因为:

  • 未定义容器上的迭代顺序,
  • 该算法可以自由制作仿函数的副本,
  • 算法可能依赖无状态,以免破坏容器内容或崩溃。

最简单的方法是将operator()定义为const

有一些例外情况,例如for_each,上述情况均不适用。您可以在这里使用有状态仿函数。有关详细信息,请参阅此优秀文章:http://drdobbs.com/cpp/184403769

在幕后,您的STL实现的作者可以随心所欲地编写remove_if(和其他算法),只要它符合标准规定的要求即可。没有真正的理由过分担心你为什么会得到你所看到的行为,除了承认它是未定义的。如果你想知道具体细节,我只想看看你正在使用的STL实现中remove_if的代码。

至于你的旁注;这不是“腐败”,它只是remove_if如何工作的工件(即使对于有效的谓词也会发生这种情况)。唯一的要求是pos left 的所有元素都是有效的(因为它们将被保留)。对于pos之后存在哪些元素没有要求(请参阅here)。 (Scott "Effective STL"第32章对于remove_if(等等)的行为有很好的解释。