Подтвердить что ты не робот

Зачем использовать функторы над функциями?

Сравнение

double average = CalculateAverage(values.begin(), values.end());

с

double average = std::for_each(values.begin(), values.end(), CalculateAverage());

В чем преимущества использования функтора над функцией? Не первый ли намного легче читать (еще до добавления добавления)?

Предположим, что функтор определяется следующим образом:

class CalculateAverage
{
private:
   std::size_t num;
   double sum;
public:

   CalculateAverage() : num (0) , sum (0)
   {
   }

   void operator () (double elem) 
   {
      num++; 
      sum += elem;
   }

   operator double() const
   {
       return sum / num;
   }
};
4b9b3361

Ответ 1

Как минимум четыре веские причины:

Разделение проблем

В вашем конкретном примере, основанный на функторе подход имеет то преимущество, что отделяет логику итерации от логики среднего вычисления. Таким образом, вы можете использовать ваш функтор в других ситуациях (подумайте обо всех других алгоритмах в STL), и вы можете использовать другие функторы с for_each.

Параметризация

Вы можете более легко параметризировать функтор. Так, например, у вас может быть функтор CalculateAverageOfPowers который принимает среднее значение квадратов или кубов и т.д. Ваших данных, которое будет записано так:

class CalculateAverageOfPowers
{
public:
    CalculateAverageOfPowers(float p) : acc(0), n(0), p(p) {}
    void operator() (float x) { acc += pow(x, p); n++; }
    float getAverage() const { return acc / n; }
private:
    float acc;
    int   n;
    float p;
};

Конечно, вы можете сделать то же самое с традиционной функцией, но это затруднит использование с указателями на функции, потому что у нее прототип, отличный от CalculateAverage.

Statefulness

И поскольку функторы могут быть с состоянием, вы можете сделать что-то вроде этого:

CalculateAverage avg;
avg = std::for_each(dataA.begin(), dataA.end(), avg);
avg = std::for_each(dataB.begin(), dataB.end(), avg);
avg = std::for_each(dataC.begin(), dataC.end(), avg);

усреднить по ряду различных наборов данных.

Обратите внимание, что почти все алгоритмы/контейнеры STL, которые принимают функторы, требуют, чтобы они были "чистыми" предикатами, то есть не имели заметного изменения состояния во времени. for_each является особым случаем в этом отношении (см., например, Effective Standard C++ Library - for_each vs. transform).

Спектакль

Функторы часто могут быть встроены компилятором (в конце концов, STL - это набор шаблонов). Хотя то же самое теоретически верно для функций, компиляторы обычно не встроены через указатель на функцию. Канонический пример - сравнение std::sort vs qsort; версия STL часто в 5-10 раз быстрее, если предположить, что сам предикат сравнения прост.

Резюме

Конечно, можно эмулировать первые три с традиционными функциями и указателями, но с функторами это становится намного проще.

Ответ 2

Преимущества функторов:

  • В отличие от функций Functor может иметь состояние.
  • Функтор вписывается в парадигму ООП по сравнению с функциями.
  • Функтор часто может быть встроен в отличие от указателей функций
  • Функтор не требует диспетчеризации vtable и диспетчеризации, и, следовательно, более эффективен в большинстве случаев.

Ответ 3

std::for_each легко является самым капризным и наименее полезным из стандартных алгоритмов. Это просто хорошая обертка для цикла. Однако даже у него есть преимущества.

Подумайте, как должна выглядеть ваша первая версия CalculateAverage. Он будет иметь петлю над итераторами, а затем делать вещи с каждым элементом. Что произойдет, если вы неправильно напишете этот цикл? К сожалению, есть компилятор или ошибка времени выполнения. Вторая версия никогда не может иметь таких ошибок. Да, это не очень много кода, но почему нам приходится так часто писать циклы? Почему не только один раз?

Теперь рассмотрим реальные алгоритмы; те, которые действительно работают. Вы хотите написать std::sort? Или std::find? Или std::nth_element? Вы даже знаете, как реализовать его наиболее эффективным способом? Сколько раз вы хотите реализовать эти сложные алгоритмы?

Что касается удобства чтения, то в глазах смотрящего. Как я уже сказал, std::for_each вряд ли является первым выбором для алгоритмов (особенно с синтаксисом на основе С++ 0x). Но если вы говорите о реальных алгоритмах, они очень читабельны; std::sort сортирует список. Некоторые из более неясных, таких как std::nth_element, не будут столь же знакомы, но вы всегда можете найти их в своей удобной ссылке на С++.

И даже std:: for_each отлично читается после использования Lambda в С++ 0x.

Ответ 4

В первом подходе итерационный код должен дублироваться во всех функциях, которые хотят что-то сделать с коллекцией. Второй подход скрывает детали итерации.

Ответ 5

• В отличие от функций Functor может иметь состояние.

Это очень интересно, потому что std:: binary_function, std:: less и std:: equal_to имеет шаблон для оператора(), который является константой. Но что, если вы хотите распечатать отладочное сообщение с текущим номером вызова для этого объекта, как вы это сделаете?

Вот шаблон для std:: equal_to:

struct equal_to : public binary_function<_Tp, _Tp, bool>
{
  bool
  operator()(const _Tp& __x, const _Tp& __y) const
  { return __x == __y; }
};

Я могу думать о 3 способах разрешить operator() быть const и, тем не менее, изменять переменную-член. Но каков наилучший способ? Возьмите этот пример:

#include <iostream>
#include <string>
#include <algorithm>
#include <functional>
#include <cassert>  // assert() MACRO

// functor for comparing two integer's, the quotient when integer division by 10.
// So 50..59 are same, and 60..69 are same.
// Used by std::sort()

struct lessThanByTen: public std::less<int>
{
private:
    // data members
    int count;  // nr of times operator() was called

public:
    // default CTOR sets count to 0
    lessThanByTen() :
        count(0)
    {
    }


    // @override the bool operator() in std::less<int> which simply compares two integers
    bool operator() ( const int& arg1, const int& arg2) const
    {
        // this won't compile, because a const method cannot change a member variable (count)
//      ++count;


        // Solution 1. this trick allows the const method to change a member variable
        ++(*(int*)&count);

        // Solution 2. this trick also fools the compilers, but is a lot uglier to decipher
        ++(*(const_cast<int*>(&count)));

        // Solution 3. a third way to do same thing:
        {
        // first, stack copy gets bumped count member variable
        int incCount = count+1;

        const int *iptr = &count;

        // this is now the same as ++count
        *(const_cast<int*>(iptr)) = incCount;
        }

        std::cout << "DEBUG: operator() called " << count << " times.\n";

        return (arg1/10) < (arg2/10);
    }
};

void test1();
void printArray( const std::string msg, const int nums[], const size_t ASIZE);

int main()
{
    test1();
    return 0;
}

void test1()
{
    // unsorted numbers
    int inums[] = {33, 20, 10, 21, 30, 31, 32, 22, };

    printArray( "BEFORE SORT", inums, 8 );

    // sort by quotient of integer division by 10
    std::sort( inums, inums+8, lessThanByTen() );

    printArray( "AFTER  SORT", inums, 8 );

}

//! @param msg can be "this is a const string" or a std::string because of implicit string(const char *) conversion.
//! print "msg: 1,2,3,...N", where 1..8 are numbers in nums[] array

void printArray( const std::string msg, const int nums[], const size_t ASIZE)
{
    std::cout << msg << ": ";
    for (size_t inx = 0; inx < ASIZE; ++inx)
    {
        if (inx > 0)
            std::cout << ",";
        std::cout << nums[inx];
    }
    std::cout << "\n";
}

Поскольку все 3 решения скомпилированы, он увеличивает счет на 3. Здесь вывод:

gcc -g -c Main9.cpp
gcc -g Main9.o -o Main9 -lstdc++
./Main9
BEFORE SORT: 33,20,10,21,30,31,32,22
DEBUG: operator() called 3 times.
DEBUG: operator() called 6 times.
DEBUG: operator() called 9 times.
DEBUG: operator() called 12 times.
DEBUG: operator() called 15 times.
DEBUG: operator() called 12 times.
DEBUG: operator() called 15 times.
DEBUG: operator() called 15 times.
DEBUG: operator() called 18 times.
DEBUG: operator() called 18 times.
DEBUG: operator() called 21 times.
DEBUG: operator() called 21 times.
DEBUG: operator() called 24 times.
DEBUG: operator() called 27 times.
DEBUG: operator() called 30 times.
DEBUG: operator() called 33 times.
DEBUG: operator() called 36 times.
AFTER  SORT: 10,20,21,22,33,30,31,32

Ответ 6

OOP - это ключевое слово.

http://www.newty.de/fpt/functor.html:

4.1 Что такое функторы?

Функторы - это функции с состоянием. В С++ вы можете реализовать их как класс с одним или несколькими частными членами для хранения состояния и с перегруженным оператором() для выполнения функции. Функторы могут инкапсулировать указатели на функции C и С++ с использованием шаблонов концепций и полиморфизма. Вы можете создать список указателей на функции-члены произвольных классов и вызвать их через один и тот же интерфейс, не беспокоясь об их классе или необходимости указателя на экземпляр. Все функции должны иметь одинаковые возвращаемые типы и вызывающие параметры. Иногда функторы также известны как замыкания. Вы также можете использовать функторы для реализации обратных вызовов.

Ответ 7

Вы сравниваете функции на разных уровнях абстракции.

Вы можете реализовать CalculateAverage(begin, end) либо как:

template<typename Iter>
double CalculateAverage(Iter begin, Iter end)
{
    return std::accumulate(begin, end, 0.0, std::plus<double>) / std::distance(begin, end)
}

или вы можете сделать это с помощью цикла цикла

template<typename Iter>
double CalculateAverage(Iter begin, Iter end)
{
    double sum = 0;
    int count = 0;
    for(; begin != end; ++begin) {
        sum += *begin;
        ++count;
    }
    return sum / count;
}

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

Он также использует только два общих компонента (std::accumulate и std::plus), что часто бывает и в более сложном случае. У вас часто может быть простой универсальный функтор (или функция, простая старая функция может действовать как функтор) и просто сочетать его с любым алгоритмом, который вам нужен.