Помогите понять работу функциональных объектов?

Я нашел этот код в Википедии.

class compare_class {
  public:
  bool operator()(int A, int B) const {
    return A < B;
  }
};
...
// Declaration of C++ sorting function.
template <class ComparisonFunctor> 
void sort_ints(int* begin_items, int num_items, ComparisonFunctor c);
...
int main() {
    int items[] = {4, 3, 1, 2};
    compare_class functor;
    sort_ints(items, sizeof(items)/sizeof(items[0]), functor);
}

Сначала мне было интересно, как параметры A и B передаются в operator()(int A, int B), когда функтор упоминается в sort_ints даже без каких-либо скобок.

Затем я понял, что A и B передаются объекту функции внутри функции sort_ints. Но тогда разве объявление sort_ints не должно иметь «ComparisonFunctor*** c» вместо «ComparisonFunctor c», поскольку оно получает адрес функции?

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

functor(*begin_items, *(begin_items+1));

person Nav    schedule 31.01.2011    source источник


Ответы (4)


Чтобы понять, почему sort_ints принимает свой параметр по значению, вы должны помнить, что объект, передаваемый ему в качестве компаратора, не обязательно является указателем на функцию. Если, например, вы используете объект функции compare_class для сравнения, то вы передаете в функцию конкретный объект типа compare_class. Вы не передаете адрес compare_class::operator(), поскольку operator() является функцией-членом, а не свободной функцией. То есть следующий код:

compare_class myComparator;
myComparator(a, b);

переводится в

compare_class myComparator;
myComparator.operator() (a, b);

Следовательно, параметр должен принимать компаратор по значению, а не по указателю, поскольку ему нужен объект-приемник, а не указатель на функцию-член или указатель на объект-приемник.

person templatetypedef    schedule 31.01.2011
comment
Означает ли это, что третий параметр sort_ints фактически принимает весь объект по значению? Не будет ли это плохо/неэффективно, если мы работаем с большими объектами? - person Nav; 31.01.2011
comment
@Nav: да - лучше бы ссылку. Но объект сравнения редко имеет большое количество состояний и передается только один раз, независимо от количества сортируемых элементов, поэтому вряд ли это будет иметь большое значение. - person Tony Delroy; 31.01.2011
comment
@Nav- Компилятор обычно действительно хорош в агрессивной оптимизации объекта-получателя вне поля зрения при вызове operator(). В сочетании с тем фактом, что компилятор может определить, какая функция вызывается во время компиляции (поскольку он знает тип получателя), часто гораздо быстрее использовать объекты функций, чем необработанные функции! (Хотя это делает исполняемый файл больше) - person templatetypedef; 31.01.2011
comment
Спасибо. Я решил, что лучший способ получить параметры sort_ints — это void sort_ints(int* begin_items, int num_items, const ComparisonFunctor& c); - person Nav; 31.01.2011
comment
@Nav: если вы используете объект функции в качестве конечного автомата, ваш подход не сработает. - person Donotalo; 31.01.2011
comment
@Donotalo: я не понимаю, как я использую эту функцию в качестве конечного автомата. Объект функции не переходит в любое время. - person Nav; 31.01.2011
comment
@Nav: не в этом случае. но учтите следующее: en.wikipedia.org/wiki/Function_object#Maintaining_state - person Donotalo; 31.01.2011
comment
@Donotalo: Хорошо, ты имеешь в виду мое использование «const»? Тогда я с тобой согласен :) - person Nav; 31.01.2011
comment
@Нав, да. :) также '&' может вызвать проблемы в зависимости от того, что вы делаете с конечным автоматом. - person Donotalo; 31.01.2011
comment
@Donotalo: я не подозревал, что «&» может быть проблемой. Однако я буду помнить ваше предостережение всякий раз, когда буду с ним работать. Спасибо :) - person Nav; 01.02.2011

Как вы заметили, в использовании sort_ints нет ничего, что намекало бы на его требования к функтору сравнения:

compare_class functor;
sort_ints(items, sizeof(items)/sizeof(items[0]), functor);

В самом sort_ints тоже ничего нет:

template <class ComparisonFunctor>
void sort_ints(int* begin_items, int num_items, ComparisonFunctor c);

И в class_compare мы можем только вывести функциональность, которая будет использоваться, наблюдая, что она не предлагает никакой другой функциональности:

class compare_class
{
  public:
     bool operator()(int A, int B) const { return A < B; }
};

Компилятор действительно оставляет его до тех пор, пока он не попытается скомпилировать реализацию sort_ints, используя типы, с которыми он создан, прежде чем он решит, висит ли все это вместе. sort_ints должен иметь именно тот оператор, который вы упомянули:

c(*begin_items, *(begin_items+1));    // note: functor argument is "c"

Так что ваше понимание верно во всех отношениях. НО стоит отметить, что предлагаемая функция C++0x под названием Concepts была предназначена для того, чтобы сделать требования sort_int более явными, не глядя на ее реализацию. К сожалению, C++0x пришлось отказаться от этой функции, так как не хватило времени и опыта, чтобы обеспечить ее оптимальную работу. Будем надеяться, что будущая версия C++ будет включать такую ​​возможность. Вы можете найти множество обсуждений Концепций в сети, и они должны помочь вам лучше понять общую проблему.

Это важная проблема, потому что, когда у вас есть только кусочки головоломки, такие как функция sort_ints, которую вы хотите использовать, но нет примера ComparisonFunctor, вам нужно изучить реализацию sort_ints, чтобы узнать, как создать этот функтор (если только вы не супер повезло, и есть хорошая, текущая документация). Вы можете случайно сделать свой код слишком зависимым от существующей реализации, так что ваш код неприемлемо ломается или замедляется, когда реализация sort_int изменяется чуть позже, даже если это достаточно тонко, чтобы не нарушить тестовые примеры или код других пользователей или ожидания. Также вероятно, что небольшая ошибка в предоставленном ComparisonFunctor вызовет очень запутанное и запутанное сообщение об ошибке компилятора откуда-то из внутренностей sort_int — это нехороший способ предоставить пользователю абстрактный сервис. Будем надеяться, что Concepts сделает это за один день...!

person Tony Delroy    schedule 31.01.2011

Обратите внимание, что compare_class — это класс. Таким образом, его можно объявить, передать как параметр функции так же, как вы используете другие классы.

Во-вторых, compare_class реализует operator(). Это позволяет сделать следующее:

compare_class obj;
obj(1, 2);

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

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

ИЗМЕНИТЬ

Ожидается, что внутри sort_ints() функтор будет делать что-то вроде следующего:

for (int i = 1; i < num_items; i++)
    if (c(begin_items[i-1], begin_items[i]))
    {
        // begin_items[i-1] is less than begin_items[i], do stuff
    }
person Donotalo    schedule 31.01.2011
comment
Спасибо, но я не пытаюсь понять общую концепцию указателей на функции. Я пытаюсь понять, как значения передаются ему в этом конкретном примере, поскольку внутренний код sort_ints недоступен и поскольку sort_ints, похоже, не получает указатель на функцию. Я озадачен тем, как это работает. - person Nav; 31.01.2011

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

person ThomasMcLeod    schedule 31.01.2011