Функция vs Struct C ++ [duplicate]

Что вы спрашиваете, почему это:

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), которые мы будем игнорировать.

Другими словами, вместо оценки параметров по умолчанию, почему бы не сохранить их каждый из них и оценить их при вызове функции?

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

53
задан DanDan 23 June 2011 в 15:50
поделиться

7 ответов

По меньшей мере четыре веские причины:

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

В вашем конкретном примере основанный на функторе подход имеет то преимущество, что отделяет логику итерации от логики вычисления среднего значения , Таким образом, вы можете использовать свой функтор в других ситуациях (подумайте обо всех других алгоритмах в 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 раз быстрее, если предположить, что сам предикат сравнения прост. он становится намного проще с функторами.

73
ответ дан Oliver Charlesworth 21 August 2018 в 02:23
поделиться
  • 1
    acc в вашем примере - глобальная переменная? – Cheers and hth. - Alf 23 June 2011 в 10:44
  • 2
    @Alf: Спасибо! .. – Oliver Charlesworth 23 June 2011 в 10:45
  • 3
    @Matthieu: Это справедливая точка. Честно говоря, я повторяю мнение, которое Скотт Мейерс делает в & quot; Эффективный STL & quot; , и что я наблюдал на практике. Я рассмотрю профилирование std::sort с помощью functor vs. function-pointer. Но в принципе, я не думаю, что есть какая-то причина, по которой qsort не может быть реализована так же, как std::sort с указателем функции. – Oliver Charlesworth 23 June 2011 в 11:12
  • 4
    @Matthieu: «В прошлый раз, когда я тестировал qsort, он выделял дополнительную память». - Я удивлен, я бы ожидал, что в стеке будет достаточно места для qsort, и для почти любой реализации qsort, чтобы полагаться на это. Кроме того, malloc разрешается сбой, а qsort нет, поэтому обычно он должен прерываться как особый случай. Наконец, qsort не требует таблицы указателей, размер элемента является одним из его параметров, а указатели на элементы передаются в функцию компаратора. Вы можете выполнить любой массив и ожидать ту же локальность, что и другие аналогичные операции с массивами. – Steve Jessop 23 June 2011 в 11:55
  • 5
    Пример statefulness действительно хорош, я не думал об использовании таких функторов. – DanDan 23 June 2011 в 12:45

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

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

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

Вы можете реализовать 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), что часто бывает и в более сложном случае. У вас часто может быть простой универсальный функтор (или функция, простая старая функция может действовать как функтор) и просто сочетать его с любым алгоритмом, который вам нужен.

1
ответ дан Jan Hudec 21 August 2018 в 02:23
поделиться

• В отличие от функций 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
2
ответ дан joe 21 August 2018 в 02:23
поделиться
  • 1
    Четвертое решение состоит в том, чтобы сделать count изменчивым. – DanDan 16 February 2013 в 12:15

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.

7
ответ дан Nicol Bolas 21 August 2018 в 02:23
поделиться

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

2
ответ дан Vijay Mathew 21 August 2018 в 02:23
поделиться

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

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

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

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

1
ответ дан Ярослав Рахматуллин 21 August 2018 в 02:23
поделиться
Другие вопросы по тегам:

Похожие вопросы: