понимание функторов в STL - PullRequest
2 голосов
/ 26 июня 2010

цитирование из «Стандартной библиотеки C ++» Н. М. Йосттиса, раздел 5.9


#include < iostream>
#include < list>
#include < algorithm>

using namespace std;

//function object that adds the value with which it is initialized
class AddValue {
    private:
       int the Value; //the value to add
    public:
       //constructor initializes the value to add
       AddValue(int v) : theValue(v) {    }
       //the "function call" for the element adds the value
       void operator() (int& elem) const {  elem += theValue; }
 };

int main()
{
      list<int> coll;
      for (int i=1; i<=9; ++i) 
         coll.push_back(i); 

      //The first call of for_each() adds 10 to each value:
      for_each (coll.begin(), coll.end(), AddValue(10)) ; 

Здесь выражение AddValue (10) создает объект типа AddValue, который инициализируется значением 10Конструктор AddValue сохраняет это значение как член theValue.Внутри for_each (), "()" вызывается для каждого элемента coll.Опять же, это вызов operator () для переданного временного объекта функции типа AddValue.Фактический элемент передается в качестве аргумента.Объект функции добавляет свое значение 10 к каждому элементу.Затем элементы имеют следующие значения: после добавления 10:

11 12 13 14 15 16 17 18 19

Второй вызов for_each () использует ту же функциональность для добавления значения первого элемента к каждому элементу.Он инициализирует временный объект функции типа AddValue с первым элементом коллекции:

for_each (coll.begin(), coll.end(), AddValue (*coll. begin()) ) ; 

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

22 23 24 25 26 27 28 29 30

whatЯ не понимаю, во втором случае почему вывод не

22 34 35 36 37 38 39 40 41

означает, что новый функтор создается для каждого вызова или функтор используется для каждого вызова?

Ответы [ 6 ]

6 голосов
/ 26 июня 2010

Выражение AddValue(*coll. begin()) создает один временный объект класса AddValue.Это временное значение затем передается в функцию for_each.for_each затем вызывает оператор вызова функции объекта - то есть operator() - один раз для каждого элемента от coll.begin() до coll.end().

Технически, for_each принимает параметр functor по значению (нессылка), поэтому он на самом деле работает с копией временного, а не с самим временным.

1 голос
/ 27 июня 2010

[Редактировать] Первоначально я неправильно понял первоначальные намерения автора.Я исправил ошибку.

for_each (coll.begin (), coll.end (), AddValue (* coll. Begin ()));

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

22 23 24 25 26 27 28 29 30

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

22 34 35 36 37 38 39 40 41

означает, что новый функтор создается для каждого вызова или функтор используется для каждого вызова?

Прежде всего, независимо от того, копирует ли алгоритм for_each функтор, который вы передаете, не имеет значения.Важно то, что вы должны хранить итератор или указатель на первый элемент, а не на указатель.Если вы делаете это и разыменовываете это каждый раз, это должно сработать.

struct AddValue 
{
    const int* ptr;
    explicit AddValue(const int* iptr): ptr(iptr) {}
    void operator() (int& elem) const {elem += *ptr; }
};

int main()
{
    vector<int> v;
    for (int j=1; j <= 9; ++j)
        v.push_back(j + 10);
    for_each(v.begin(), v.end(), AddValue(&v[0]) );
    // v will be [22 34 35 36 37 38 39 40 41]
}

Однако мне придется добавить два моих цента о функциональном программировании.Вы можете получить действительно лаконичный, продуманный код с помощью функционального программирования.Тем не менее, это также большая проблема для отладки и имеет эффект децентрализации кода.Если ваш код не может извлечь из этого значительную пользу, рассмотрите возможность использования простых циклов для циклов, основанных на итераторах, когда это возможно, поскольку это даст вашим коллегам-программистам гораздо больше времени для отладки и чтения вашего кода.

Я сделал ошибку, получивВ прошлом я был очень властен с функциональным программированием, и это заставило мою команду ненавидеть меня.Я был одержим написанием дьявольски сложного кода с использованием комбинаторной логики предикатов и создал огромные библиотеки функциональных объектов, которые можно многократно использовать снова и снова и в комбинациях.На самом деле все, что я делал, это тратил много времени на написание функторов, когда я мог бы писать простые, одинаково многократно используемые функции, которые можно было бы вызывать (и вставлять так же легко) из простого цикла for, основанного на итераторе (даже проще писать с помощьюC ++ 0x на основе диапазона для цикла и BOOST_FOR_EACH).Я все еще использую функциональное программирование на C ++, но экономно.Когда вы сталкиваетесь с большими трудностями и собираете время, чтобы собрать две или три строки кода в одну, вы действительно должны спросить себя и глубоко задуматься, стоит ли это того, и не только для вас, но и для всех, кто работаетс вашим кодом.

0 голосов
/ 26 июня 2010

Ваш конструктор AddValue принимает int, поэтому при его создании из *coll.begin() значение первого члена вашей коллекции используется для инициализации переменной-члена theValue.

Этотеперь исправлено (ничто другое не изменяет theValue), поэтому каждый раз theValue используется из этого AddValue объекта или любых копий этого AddValue объекта, он все равно будет иметь то же значение, с которым он был инициализирован.

Это значение, которое *coll.begin() имело во время создания первого AddValue объекта, а не значение *coll.begin(), возможно, было изменено.

0 голосов
/ 26 июня 2010

Я считаю, что причина, по которой функтор копируется таким образом, заключается в том, чтобы сделать for_each более общим.

Представьте себе реализацию, которая брала функтор по ссылке.Это сломалось бы, если бы функтор был r-значением (скажем, если он возвращен из другой функции):

std::for_each(first, last, get_functor(...))

Конечно, в этих случаях алгоритм мог бы взять функтор по константной ссылке (посколькубыть привязанным к rvalues), но тогда функтор должен быть постоянным.

Единственное действительно общее решение - передать функтор по значению.Затем он работает с функторами const, non-const, rvalue и lvalue.

0 голосов
/ 26 июня 2010

Да. Новая копия вашего функтора передается через for_each. Книга, которую вы читаете, объясняет это.

0 голосов
/ 26 июня 2010

Да, это то, что вы сказали.Функторы по умолчанию передаются по значению и, таким образом, они копируются в код std :: for_each.Однако вы можете написать свою собственную версию std :: for_each, явно указав, что вы хотите передать функтор по ссылке.

...