Что вы спрашиваете, почему это:
def func(a=[], b = 2):
pass
не является внутренне эквивалентным этому:
def func(a=None, b = None):
a_default = lambda: []
b_default = lambda: 2
def actual_func(a=None, b=None):
if a is None: a = a_default()
if b is None: b = b_default()
return actual_func
func = func()
, за исключением случая прямого вызова func (None , None), которые мы будем игнорировать.
Другими словами, вместо оценки параметров по умолчанию, почему бы не сохранить их каждый из них и оценить их при вызове функции?
Один ответ, вероятно, прямо там - он фактически превратит каждую функцию с параметрами по умолчанию в закрытие. Даже если это все скрыто в интерпретаторе, а не полномасштабное закрытие, данные должны быть где-то сохранены. Он будет медленнее и будет использовать больше памяти.
По меньшей мере четыре веские причины:
Разделение проблем
В вашем конкретном примере основанный на функторе подход имеет то преимущество, что отделяет логику итерации от логики вычисления среднего значения , Таким образом, вы можете использовать свой функтор в других ситуациях (подумайте обо всех других алгоритмах в 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
.
Состоятельность
И поскольку функторы могут быть stateful, вы можете сделать что-то вроде этого:
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
является особым случаем в этом отношении (см., например, Эффективная стандартная библиотека C ++ - for_each vs. transform ).
Производительность
Функторы часто могут быть встроенный компилятором (STL - это куча шаблонов, в конце концов). В то время как теоретически это верно для функций, компиляторы обычно не будут встроены в указатель функций. Канонический пример - сравнение std::sort
vs qsort
;
Сводка
Конечно, можно эмулировать первые три с традиционными функциями и указателями, но при этом, как правило, 5-10 раз быстрее, если предположить, что сам предикат сравнения прост. он становится намного проще с функторами.
Преимущества функторов:
Вы сравниваете функции на разных уровнях абстракции.
Вы можете реализовать 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)
}
, либо вы можете сделать это с помощью цикла for
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
), что часто бывает и в более сложном случае. У вас часто может быть простой универсальный функтор (или функция, простая старая функция может действовать как функтор) и просто сочетать его с любым алгоритмом, который вам нужен.
• В отличие от функций 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
std::for_each
легко является самым капризным и наименее полезным из стандартных алгоритмов. Это просто хорошая обертка для цикла. Однако даже у него есть преимущества.
Подумайте, как должна выглядеть ваша первая версия CalculateAverage
. Он будет иметь петлю над итераторами, а затем делать вещи с каждым элементом. Что произойдет, если вы неправильно напишете этот цикл? К сожалению, есть компилятор или ошибка времени выполнения. Вторая версия никогда не может иметь таких ошибок. Да, это не очень много кода, но почему нам приходится так часто писать циклы? Почему не только один раз?
Теперь рассмотрим реальные алгоритмы; те, которые действительно работают. Вы хотите написать std::sort
? Или std::find
? Или std::nth_element
? Вы даже знаете, как реализовать его наиболее эффективным способом? Сколько раз вы хотите реализовать эти сложные алгоритмы?
Что касается простоты чтения, это в глазах смотрящего. Как я уже сказал, std::for_each
вряд ли является первым выбором для алгоритмов (особенно с синтаксисом диапазона C ++ 0x). Но если вы говорите о реальных алгоритмах, они очень читабельны; std::sort
сортирует список. Некоторые из более неясных, таких как std::nth_element
, не будут столь же знакомы, но вы всегда можете найти их в своей удобной ссылке на C ++.
И даже std :: for_each отлично читается, как только вы используете Lambda's в C ++ 0x.
В первом подходе код итерации должен дублироваться во всех функциях, которые хотят что-то сделать с коллекцией. Второй подход скрывает детали итерации.
OOP - это ключевое слово здесь.
http://www.newty.de/fpt/functor.html :
4.1 Что такое функторы?
Функторы являются функциями с состоянием. В C ++ вы можете реализовать их как класс с одним или несколькими частными членами для хранения состояния и с перегруженным оператором () для выполнения функции. Функторы могут инкапсулировать указатели на функции C и C ++ с использованием шаблонов концепций и полиморфизма. Вы можете создать список указателей на функции-члены произвольных классов и вызвать их через один и тот же интерфейс, не беспокоясь об их классе или необходимости указателя на экземпляр. Все функции должны иметь одинаковые возвращаемые типы и вызывающие параметры. Иногда функторы также известны как замыкания. Вы также можете использовать функторы для реализации обратных вызовов.
acc
в вашем примере - глобальная переменная? – Cheers and hth. - Alf 23 June 2011 в 10:44std::sort
с помощью functor vs. function-pointer. Но в принципе, я не думаю, что есть какая-то причина, по которойqsort
не может быть реализована так же, какstd::sort
с указателем функции. – Oliver Charlesworth 23 June 2011 в 11:12