Функторы с состоянием и STL: неопределенное поведение - PullRequest
8 голосов
/ 24 мая 2011

Я слежу за этим Учебное пособие по объектам функций

Копирование-вставка ниже:

Я не могу понять следующее:

Предикаты всегда должны быть реализованы как объекты функции без сохранения состояния, чтобы избежать неожиданных результатов.Нет гарантии, как часто алгоритм может внутренне копировать предикат.Таким образом, наличие предикатов, которые реализованы как объекты функций с состоянием, может иметь неисполненные результаты.

Пример выглядит следующим образом:

#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' внутренне копирует предикат во время его выполнения.И эта внутренняя копия создает новый объект предиката, содержащий его исходное состояние.

Хотя я могу читать то, что, кажется, происходит, я не могу представить, что происходит за кулисами, которые фактически отмечали даже 4-еэлемент, который нужно переместить в конец контейнера.Это имеет отношение к алгоритму, являющемуся единственным проходом или многократным проходом?(также я был бы признателен, если бы кто-то мог указать мне правильное направление, как вывести то же самое)

На примечании стороны, если я прокомментирую стирание и отмечу вывод.

1 3 5 4 5

Что приводит к повреждению контейнера?

1 Ответ

20 голосов
/ 24 мая 2011

Значение этой цитаты следует принимать за чистую монету.Для большинства алгоритмов STL не следует реализовывать функтор предикатов таким образом, чтобы он имел наблюдаемое состояние («побочные эффекты» AKA), поскольку:

  • порядок итераций для контейнера не определен,
  • алгоритм может свободно копировать функтор,
  • алгоритм может зависеть от отсутствия состояния, чтобы не повредить содержимое контейнера или не произошло сбой.

Самый простой способ навязать это самому себе - это определить operator() как const.

Существуют исключения, такие как for_each, к которым не применимо ни одно из перечисленных выше.Вы можете свободно использовать функторы с сохранением состояния здесь.Для получения дополнительной информации см. Эту превосходную статью: http://drdobbs.com/cpp/184403769.

За кулисами авторы вашей реализации STL могут написать remove_if (и другие алгоритмы) так, как им нравится, если толькосоответствует требованиям, установленным стандартом.Нет никакой реальной причины слишком сильно беспокоиться о том, почему вы получаете поведение, которое вы видите, кроме признания того, что оно не определено.Если вы хотите узнать подробности, я бы просто взглянул на код для remove_if в используемой вами реализации STL.

Что касается вашей дополнительной заметки;это не «искажение», это просто артефакт работы remove_if (это может произойти даже для действительного предиката).Единственное требование состоит в том, что все элементы слева из pos являются действительными (потому что они должны быть сохранены).Нет никаких требований к тому, какие элементы существуют с pos и далее (см. здесь ).(Глава 32 " Effective STL " Скотта Мейерса имеет хорошее объяснение того, почему remove_if (и так далее) ведут себя так).

...