Що таке функтори C ++ та їх використання?


875

Я постійно чую про функторів на C ++. Чи може хтось дати мені огляд того, що вони є, і в яких випадках вони будуть корисні?


4
Ця тема була висвітлена у відповіді на це запитання: stackoverflow.com/questions/317450/why-override-operator#317528
Люк Турей

2
Він використовується для створення закриття в C ++.
мідь. Що

Дивлячись на відповіді нижче, якщо хтось цікавиться, що це operator()(...)означає: це перевантаження оператора "виклик функції" . Це просто перевантаження оператора для ()оператора. Не помиліться operator()з викликом функції, яка називається operator, але розглядайте її як звичайний синтаксис, що перевантажує оператор.
зардош

Відповіді:


1041

Функтор - це майже просто клас, який визначає оператора (). Це дозволяє створювати об'єкти, які "схожі на" функцію:

// this is a functor
struct add_x {
  add_x(int val) : x(val) {}  // Constructor
  int operator()(int y) const { return x + y; }

private:
  int x;
};

// Now you can use it like this:
add_x add42(42); // create an instance of the functor class
int i = add42(8); // and "call" it
assert(i == 50); // and it added 42 to its argument

std::vector<int> in; // assume this contains a bunch of values)
std::vector<int> out(in.size());
// Pass a functor to std::transform, which calls the functor on every element 
// in the input sequence, and stores the result to the output sequence
std::transform(in.begin(), in.end(), out.begin(), add_x(1)); 
assert(out[i] == in[i] + 1); // for all i

Є кілька приємних речей про функторів. Одне полягає в тому, що на відміну від звичайних функцій вони можуть містити стан. Наведений вище приклад створює функцію, яка додає 42 до того, що ви їй надаєте. Але це значення 42 не є жорстким кодом, воно було вказане як аргумент конструктора, коли ми створили наш екземпляр функтора. Я міг би створити інший суматор, який додав 27, просто зателефонувавши конструктору з іншим значенням. Це робить їх приємно налаштовувати.

Як показують останні рядки, ви часто передаєте функтори як аргументи іншим функціям, таким як std :: transform або інші стандартні алгоритми бібліотеки. Ви можете зробити те ж саме зі звичайним вказівником функції, за винятком, як я вже говорив вище, функторів можна "налаштувати", оскільки вони містять стан, роблячи їх більш гнучкими (Якщо я хотів би використовувати функцію вказівника, я повинен був написати функцію який додав до свого аргументу рівно 1. Функтор є загальним і додає все, що ви його ініціалізували), і вони також потенційно ефективніші. У наведеному вище прикладі компілятор точно знає, яку функцію std::transformслід викликати. Слід зателефонувати add_x::operator(). Це означає, що він може вбудовувати цей виклик функції. І це робить це так само ефективно, як якщо б я вручну викликав функцію для кожного значення вектора.

Якби я замість цього передав покажчик функції, компілятор не міг би відразу побачити, на яку функцію він вказує, тому, якщо він не виконує досить складних глобальних оптимізацій, йому доведеться знеструмити покажчик під час виконання, а потім здійснити виклик.


32
Чи можете ви пояснити цей рядок, будь ласка, std :: transform (in.begin (), in.end (), out.begin (), add_x (1)); чому ви пишете там add_x, а не add42?
Alecs

102
@Alecs Обидва працювали б (але ефект був би іншим). Якби я використовував add42, я б використав функцію, яку я створив раніше, і додав 42 до кожного значення. Створюю add_x(1)новий екземпляр функтора, той, який додає лише 1 до кожного значення. Це просто показати, що часто ви інстанціюєте функтор "на льоту", коли вам це потрібно, а не створювати його спочатку, і тримати його навколо, перш ніж ви його фактично використовуєте для чого-небудь.
jalf

8
@zadane звичайно. Вони просто повинні мати це operator(), тому що саме те, що викликає користувач, викликає його. Що ще має функтор з функцій-членів, конструкторів, операторів та змінних членів, повністю залежить від вас.
jalf

4
@ rikimaru2013 Якщо говорити про функціональне програмування, ти маєш рацію, функція також є функцією, але, кажучи на мові C ++, функтор - це конкретно клас, який використовується як функція. Термінологією було трохи зловживати на початку, але поділ є корисним розмежуванням і так існує і сьогодні. Якщо ви почнете посилатися на функції як "функтори" в контексті C ++, ви просто заплутаєте розмову.
srm

6
Це клас чи екземпляр класу? У більшості джерел їх add42би називали функтором, а не add_x(що це клас функтора або просто клас функтора). Я вважаю цю термінологію послідовною, оскільки функтори також називаються об'єктами функцій , а не класами функцій. Чи можете ви уточнити цей момент?
Сергій Таченов

121

Невелике доповнення. Ви можете використовувати boost::functionдля створення функторів з функцій та методів, таких як:

class Foo
{
public:
    void operator () (int i) { printf("Foo %d", i); }
};
void Bar(int i) { printf("Bar %d", i); }
Foo foo;
boost::function<void (int)> f(foo);//wrap functor
f(1);//prints "Foo 1"
boost::function<void (int)> b(&Bar);//wrap normal function
b(1);//prints "Bar 1"

і ви можете використовувати boost :: bind, щоб додати стан до цього функтора

boost::function<void ()> f1 = boost::bind(foo, 2);
f1();//no more argument, function argument stored in f1
//and this print "Foo 2" (:
//and normal function
boost::function<void ()> b1 = boost::bind(&Bar, 2);
b1();// print "Bar 2"

і найкорисніше, за допомогою функції boost :: bind і boost :: ви можете створити функтор з класового методу, насправді це делегат:

class SomeClass
{
    std::string state_;
public:
    SomeClass(const char* s) : state_(s) {}

    void method( std::string param )
    {
        std::cout << state_ << param << std::endl;
    }
};
SomeClass *inst = new SomeClass("Hi, i am ");
boost::function< void (std::string) > callback;
callback = boost::bind(&SomeClass::method, inst, _1);//create delegate
//_1 is a placeholder it holds plase for parameter
callback("useless");//prints "Hi, i am useless"

Ви можете створити список або вектор функторів

std::list< boost::function<void (EventArg e)> > events;
//add some events
....
//call them
std::for_each(
        events.begin(), events.end(), 
        boost::bind( boost::apply<void>(), _1, e));

З усіма цими матеріалами є одна проблема, повідомлення про помилки компілятора не читаються людиною :)


4
Чи не має operator ()бути публічним у вашому першому прикладі, оскільки класи за замовчуванням приватні?
NathanOliver

4
можливо, в якийсь момент ця відповідь заслуговує на оновлення, оскільки зараз лямбди - найпростіший спосіб отримати функтора з будь-чого
idclev 463035818

102

Функтор - це об'єкт, який діє як функція. В основному, клас, який визначає operator().

class MyFunctor
{
   public:
     int operator()(int x) { return x * 2;}
}

MyFunctor doubler;
int x = doubler(5);

Реальна перевага полягає в тому, що функтор може утримувати стан.

class Matcher
{
   int target;
   public:
     Matcher(int m) : target(m) {}
     bool operator()(int x) { return x == target;}
}

Matcher Is5(5);

if (Is5(n))    // same as if (n == 5)
{ ....}

11
Просто потрібно додати, що вони можуть використовуватися так само, як і покажчик функції.
Мартін Йорк

7
@LokiAstari - Для тих, хто не знайомий з концепцією, це може бути трохи оманливим. Функціонери можна "використовувати як", але не завжди "замість" покажчиків функцій. Наприклад, функція, яка приймає покажчик функції, не може прийняти функтор на своє місце, навіть якщо у функтора є ті ж аргументи та повернене значення, що і вказівник функції. Але за великим рахунком при проектуванні функтори є кращим і теоретично "більш сучасним" шляхом.
МейсонВінсауер

Чому другий повертається, intколи має повернутися bool? Це C ++, а не C. Коли ця відповідь була написана, boolне існувало?
Фонд позову Моніки

@QPaysTaxes Думаю, помилка друку Я, мабуть, скопіював код з першого прикладу і забув його змінити. Я зараз це виправив.
Джеймс Курран

1
@Riasat Якщо Matcher є в бібліотеці, визначити Is5 () досить просто. І ви можете створити Is7 (), Is32 () тощо. Далі, це лише приклад. Функтор може бути набагато складнішим.
Джеймс Курран

51

Назва "функтор" традиційно застосовується в теорії категорій задовго до появи на сцені C ++. Це не має нічого спільного з концепцією C ++ функтора. Краще використовувати об'єкт імені функції замість того, що ми називаємо "функтор" в C ++. Ось так інші мови програмування називають подібні конструкції.

Використовується замість простої функції:

Особливості:

  • Об'єкт функції може мати стан
  • Об'єкт функції вписується в OOP (він веде себе як і кожен інший об'єкт).

Мінуси:

  • Надає більше складності програмі.

Використовується замість вказівника функції:

Особливості:

  • Об'єкт функції часто може бути накресленим

Мінуси:

  • Об'єкт функції не може бути замінений на інший тип об'єкта функції під час виконання (принаймні, якщо він не поширює деякий базовий клас, що, таким чином, дає деяку накладну вартість)

Використовується замість віртуальної функції:

Особливості:

  • Функціональний об'єкт (невіртуальний) не вимагає диспетчеризації Vtable та часу виконання, тому він більш ефективний у більшості випадків

Мінуси:

  • Об'єкт функції не може бути замінений на інший тип об'єкта функції під час виконання (принаймні, якщо він не поширює деякий базовий клас, що, таким чином, дає деяку накладну вартість)

1
Чи можете ви пояснити ці випадки використання на реальному прикладі? як ми можемо використовувати функтори як поліморфізм та функцію вказівника?
Мілад Хаджаві

1
Що насправді означає, що функтор утримує стан?
erogol

дякую, що вказали, що потрібен базовий клас, щоб мати якийсь поліморфізм. У мене просто проблема, що мені доводиться використовувати функтор там же, як і простий вказівник функції, і єдиний спосіб, який я знайшов, - це написати базовий клас функтора (оскільки я не можу використовувати C ++ 11 речей). Не був впевнений, чи має сенс цей наклад, поки я не прочитаю вашу відповідь.
idclev 463035818

1
@Erogol Функтор - це об'єкт, який, як правило, підтримує синтаксис foo(arguments). Тому він може містити змінні; наприклад, якби у вас була update_password(string)функція, ви, можливо, захочете відстежувати, як часто це сталося; з функтором, який може бути private long timeвідображенням часу, коли воно відбулося востаннє. За допомогою покажчика функції або простий функції, вам потрібно використовувати змінну поза простором імен, яке тільки безпосередньо пов'язані документацією і використання, а не definition.l
Судовий процес Фонду Моніки

4
⁺¹ за згадку про те, що ім'я було складено без причини. Я щойно шукав, яке співвідношення між математичним (або функціональним, якщо ви хочете) функтором, та тим, від C ++.
Привіт-Ангел

41

Як уже згадували інші, функтор - це об'єкт, який діє як функція, тобто перевантажує оператора виклику функції.

Функтори зазвичай використовуються в алгоритмах STL. Вони корисні, оскільки можуть утримувати стан до і між викликами функцій, як закриття функціональних мов. Наприклад, ви можете визначити MultiplyByфунктор, який помножує його аргумент на задану суму:

class MultiplyBy {
private:
    int factor;

public:
    MultiplyBy(int x) : factor(x) {
    }

    int operator () (int other) const {
        return factor * other;
    }
};

Тоді ви можете передати MultiplyByоб’єкт в алгоритм типу std :: transform:

int array[5] = {1, 2, 3, 4, 5};
std::transform(array, array + 5, array, MultiplyBy(3));
// Now, array is {3, 6, 9, 12, 15}

Ще одна перевага функтора над вказівником на функцію полягає в тому, що виклик може бути вкладений в більшій кількості випадків. Якщо ви передали вказівник функції transform, якщо цей виклик не вбудований, і компілятор знає, що ви завжди передаєте йому одну і ту ж функцію, він не може вбудувати виклик через вказівник.


37

Для новачків, як я, серед нас: після невеликого дослідження я з'ясував, що робив кодове повідомлення.

Функтор - це об'єкт класу або структури, який можна «назвати» як функцію. Це стає можливим завдяки перевантаженню () operator. () operator(Не впевнений , що його називають) може приймати будь-яку кількість аргументів. Інші оператори беруть лише два, тобто + operatorможуть приймати лише два значення (по одному на кожній стороні оператора) і повертати будь-яке значення, на яке ви його перевантажили. Ви можете помістити будь-яку кількість аргументів всередину a () operator, що дає їй її гнучкість.

Для створення функтора спочатку ви створюєте свій клас. Потім ви створюєте конструктор до класу з параметром на ваш вибір типу та імені. Після цього в цьому ж твердженні йде список ініціалізатора (який використовує єдиний оператор двокрапки, що я теж був новим), який будує об’єкти класового члена з попередньо оголошеним параметром конструктору. Потім () operatorперевантажується. Нарешті ви оголошуєте приватні об'єкти створеного вами класу чи структури.

Мій код (я вважав, що імена змінних jalf є заплутаними)

class myFunctor
{ 
    public:
        /* myFunctor is the constructor. parameterVar is the parameter passed to
           the constructor. : is the initializer list operator. myObject is the
           private member object of the myFunctor class. parameterVar is passed
           to the () operator which takes it and adds it to myObject in the
           overloaded () operator function. */
        myFunctor (int parameterVar) : myObject( parameterVar ) {}

        /* the "operator" word is a keyword which indicates this function is an 
           overloaded operator function. The () following this just tells the
           compiler that () is the operator being overloaded. Following that is
           the parameter for the overloaded operator. This parameter is actually
           the argument "parameterVar" passed by the constructor we just wrote.
           The last part of this statement is the overloaded operators body
           which adds the parameter passed to the member object. */
        int operator() (int myArgument) { return myObject + myArgument; }

    private: 
        int myObject; //Our private member object.
}; 

Якщо що-небудь із цього неточне чи просто неправильне, не соромтеся мене виправити!


1
Оператор () називається оператором виклику функцій. Я думаю, ви можете також назвати його оператором в дужках.
Гаутам

4
"Цей параметр насправді є аргументом" parameterVar ", переданим конструктором, який ми щойно написали" Так ?
Гонки легкості на орбіті

22

Функтор - це функція вищого порядку, яка застосовує функцію до параметризованих (тобто шаблонних) типів. Це узагальнення карти вищого порядку. Наприклад, ми могли б визначити функтор std::vectorтак:

template<class F, class T, class U=decltype(std::declval<F>()(std::declval<T>()))>
std::vector<U> fmap(F f, const std::vector<T>& vec)
{
    std::vector<U> result;
    std::transform(vec.begin(), vec.end(), std::back_inserter(result), f);
    return result;
}

Ця функція приймає a std::vector<T>і повертається, std::vector<U>коли їй надається функція, Fяка приймає a Tі повертає a U. Функтор не потрібно визначати для типів контейнерів, він може бути визначений і для будь-якого шаблонного типу, включаючи std::shared_ptr:

template<class F, class T, class U=decltype(std::declval<F>()(std::declval<T>()))>
std::shared_ptr<U> fmap(F f, const std::shared_ptr<T>& p)
{
    if (p == nullptr) return nullptr;
    else return std::shared_ptr<U>(new U(f(*p)));
}

Ось простий приклад, який перетворює тип у double:

double to_double(int x)
{
    return x;
}

std::shared_ptr<int> i(new int(3));
std::shared_ptr<double> d = fmap(to_double, i);

std::vector<int> is = { 1, 2, 3 };
std::vector<double> ds = fmap(to_double, is);

Є два закони, яких слід дотримуватися функторам. Перший - закон про ідентичність, в якому йдеться про те, що якщо функціонеру присвоєно функцію ідентичності, це має бути те саме, що застосовувати функцію ідентичності до типу, тобто fmap(identity, x)бути таким же, як identity(x):

struct identity_f
{
    template<class T>
    T operator()(T x) const
    {
        return x;
    }
};
identity_f identity = {};

std::vector<int> is = { 1, 2, 3 };
// These two statements should be equivalent.
// is1 should equal is2
std::vector<int> is1 = fmap(identity, is);
std::vector<int> is2 = identity(is);

Наступний закон - закон про композицію, в якому зазначено, що якщо функціонеру надано композицію з двох функцій, він повинен бути таким же, як застосування функтора для першої функції, а потім знову для другої функції. Отже, fmap(std::bind(f, std::bind(g, _1)), x)має бути те саме, що fmap(f, fmap(g, x)):

double to_double(int x)
{
    return x;
}

struct foo
{
    double x;
};

foo to_foo(double x)
{
    foo r;
    r.x = x;
    return r;
}

std::vector<int> is = { 1, 2, 3 };
// These two statements should be equivalent.
// is1 should equal is2
std::vector<foo> is1 = fmap(std::bind(to_foo, std::bind(to_double, _1)), is);
std::vector<foo> is2 = fmap(to_foo, fmap(to_double, is));

2
Стаття, що стверджує, що функтор слід правильно використовувати для цього значення (див. Також en.wikipedia.org/wiki/Functor ), а використання його для об'єктів функцій просто неохайне : jackieokay.com/2017/01/26/functors.html Це можливо, для цього буде занадто пізно, враховуючи кількість відповідей, які враховують лише значення об'єкта функції.
armb

2
Ця відповідь має відповідати> 700 оновлень. Як хтось знає Haskell краще, ніж C ++, лінгва C ++ незмінно мене спантеличувала.
mschmidt

Теорія категорій і C ++? Це таємний обліковий запис Бартоша Мілевського?
Матін Ульхак

1
Може бути корисним узагальнити закони функтора в стандартних позначеннях: fmap(id, x) = id(x)і fmap(f ◦ g, x) = fmap(f, fmap(g, x)).
Mateen Ulhaq

@mschmidt в той час як функтор також означає це, C ++ перевантажує ім'я, щоб означати те саме, що і "об'єкт функції"
Caleth

9

Ось фактична ситуація, коли я був змушений використовувати функцію для вирішення своєї проблеми:

У мене є набір функцій (скажімо, 20 з них), і всі вони однакові, за винятком кожного виклику різної конкретної функції в 3 конкретних місцях.

Це неймовірні відходи та дублювання коду. Зазвичай я просто передавав би функцію вказівника і просто називав би це в 3 місцях. (Отже, код повинен з’являтися лише один раз, а не двадцять разів.)

Але тоді я зрозумів, що в кожному випадку конкретна функція вимагає зовсім іншого профілю параметрів! Іноді 2 параметри, іноді 5 параметрів тощо.

Іншим рішенням було б мати базовий клас, де специфічна функція є методом, що перекривається, у похідному класі. Але я дійсно хочу створити все це НАСЛІД, просто щоб я міг передати вказівник функції ????

РЕШЕННЯ: Отже, що я зробив, я створив клас обгортки ("Functor"), який здатний викликати будь-яку функцію, яку мені потрібно викликати. Я встановлюю її заздалегідь (з її параметрами тощо), а потім передаю її замість вказівника функції. Тепер викликаний код може спровокувати функцію, не знаючи, що відбувається зсередини. Він навіть може викликати його кілька разів (мені потрібно було дзвонити 3 рази.)


Ось - практичний приклад, коли очевидним і простим рішенням виявився Functor, який дозволив мені зменшити дублювання коду з 20 функцій до 1.


3
Якщо ваш функтор викликав різні конкретні функції, а ці інші функції змінювались у кількості прийнятих параметрів, чи це означає, що ваш функтор прийняв змінну кількість аргументів для відправки на ці інші функції?
johnbakers

4
Ви можете пояснити вищенаведений сценарій, цитуючи якусь частину коду, я новачок в с ++ хочу зрозуміти цю концепцію ..
sanjeev

3

За винятком випадків, що використовуються в зворотному дзвінку, функтори C ++ також можуть допомогти надати Matlab, який подобається стилю доступу до матричного класу. Є приклад .


Це (приклад матриці) - це звичайне використання operator()властивостей об'єкта функції, але не використання його властивостей.
renardesque

3

Як і неодноразово, функтори - це класи, які можна розглядати як функції (оператор перевантаження ()).

Вони найбільш корисні для ситуацій, коли вам потрібно пов’язати деякі дані з повторними або затримками викликів до функції.

Наприклад, пов'язаний список функторів може бути використаний для реалізації основної синхронної системи синхронної кореневища з низькими накладними витратами, диспетчера завдань або розбору файлів перериваючих файлів. Приклади:

/* prints "this is a very simple and poorly used task queue" */
class Functor
{
public:
    std::string output;
    Functor(const std::string& out): output(out){}
    operator()() const
    {
        std::cout << output << " ";
    }
};

int main(int argc, char **argv)
{
    std::list<Functor> taskQueue;
    taskQueue.push_back(Functor("this"));
    taskQueue.push_back(Functor("is a"));
    taskQueue.push_back(Functor("very simple"));
    taskQueue.push_back(Functor("and poorly used"));
    taskQueue.push_back(Functor("task queue"));
    for(std::list<Functor>::iterator it = taskQueue.begin();
        it != taskQueue.end(); ++it)
    {
        *it();
    }
    return 0;
}

/* prints the value stored in "i", then asks you if you want to increment it */
int i;
bool should_increment;
int doSomeWork()
{
    std::cout << "i = " << i << std::endl;
    std::cout << "increment? (enter the number 1 to increment, 0 otherwise" << std::endl;
    std::cin >> should_increment;
    return 2;
}
void doSensitiveWork()
{
     ++i;
     should_increment = false;
}
class BaseCoroutine
{
public:
    BaseCoroutine(int stat): status(stat), waiting(false){}
    void operator()(){ status = perform(); }
    int getStatus() const { return status; }
protected:
    int status;
    bool waiting;
    virtual int perform() = 0;
    bool await_status(BaseCoroutine& other, int stat, int change)
    {
        if(!waiting)
        {
            waiting = true;
        }
        if(other.getStatus() == stat)
        {
            status = change;
            waiting = false;
        }
        return !waiting;
    }
}

class MyCoroutine1: public BaseCoroutine
{
public:
    MyCoroutine1(BaseCoroutine& other): BaseCoroutine(1), partner(other){}
protected:
    BaseCoroutine& partner;
    virtual int perform()
    {
        if(getStatus() == 1)
            return doSomeWork();
        if(getStatus() == 2)
        {
            if(await_status(partner, 1))
                return 1;
            else if(i == 100)
                return 0;
            else
                return 2;
        }
    }
};

class MyCoroutine2: public BaseCoroutine
{
public:
    MyCoroutine2(bool& work_signal): BaseCoroutine(1), ready(work_signal) {}
protected:
    bool& work_signal;
    virtual int perform()
    {
        if(i == 100)
            return 0;
        if(work_signal)
        {
            doSensitiveWork();
            return 2;
        }
        return 1;
    }
};

int main()
{
     std::list<BaseCoroutine* > coroutineList;
     MyCoroutine2 *incrementer = new MyCoroutine2(should_increment);
     MyCoroutine1 *printer = new MyCoroutine1(incrementer);

     while(coroutineList.size())
     {
         for(std::list<BaseCoroutine *>::iterator it = coroutineList.begin();
             it != coroutineList.end(); ++it)
         {
             *it();
             if(*it.getStatus() == 0)
             {
                 coroutineList.erase(it);
             }
         }
     }
     delete printer;
     delete incrementer;
     return 0;
}

Звичайно, ці приклади самі по собі не такі корисні. Вони лише показують, наскільки функтори можуть бути корисними, самі функтори дуже елементарні і негнучкі, і це робить їх менш корисними, ніж, наприклад, те, що забезпечує підвищення.


2

Функціонери використовуються в gtkmm для підключення деякої кнопки GUI до фактичної функції C ++ або методу.


Якщо ви використовуєте бібліотеку pthread, щоб зробити додаток багатопоточним, функціонери можуть вам допомогти.
Для початку потоку одним з аргументів цього pthread_create(..)є покажчик функції, який повинен виконуватися на його власному потоці.
Але є одна незручність. Цей покажчик не може бути вказівником на метод, якщо це не статичний метод , або якщо ви не вкажете , наприклад, класclass::method . І ще одна річ, інтерфейс вашого методу може бути лише:

void* method(void* something)

Таким чином, ви не можете запускати (простим очевидним способом) методи свого класу в потоці, не роблячи щось зайве.

Дуже хороший спосіб роботи з потоками в C ++ - це створення власного Threadкласу. Якщо ви хотіли запустити методи з MyClassкласу, я це перетворив на Functorпохідні класи.

Також у Threadкласі є такий метод: static void* startThread(void* arg)
Вказівник на цей метод буде використовуватися як аргумент для виклику pthread_create(..). І те, що startThread(..)повинно отримати аргумент, - це void*посилання на екземпляр у купі будь-якого Functorпохідного класу, який буде повернутись до нього Functor*при виконанні, а потім називається його run()методом.


2

Для додавання я використовував об'єкти функцій, щоб підходити до існуючого застарілого методу до шаблону команд; (єдине місце, де я відчував красу парадигми ОО OCP); Також додайте сюди відповідний шаблон адаптера функції.

Припустимо, ваш метод має підпис:

int CTask::ThreeParameterTask(int par1, int par2, int par3)

Ми побачимо, як ми можемо підігнати його до шаблону Command - для цього спочатку потрібно написати адаптер функції члена, щоб його можна було викликати як об’єкт функції.

Примітка - це некрасиво, і ви можете використовувати помічників Boost bind etc., але якщо ви не можете чи не хочете, це один із способів.

// a template class for converting a member function of the type int        function(int,int,int)
//to be called as a function object
template<typename _Ret,typename _Class,typename _arg1,typename _arg2,typename _arg3>
class mem_fun3_t
{
  public:
explicit mem_fun3_t(_Ret (_Class::*_Pm)(_arg1,_arg2,_arg3))
    :m_Ptr(_Pm) //okay here we store the member function pointer for later use
    {}

//this operator call comes from the bind method
_Ret operator()(_Class *_P, _arg1 arg1, _arg2 arg2, _arg3 arg3) const
{
    return ((_P->*m_Ptr)(arg1,arg2,arg3));
}
private:
_Ret (_Class::*m_Ptr)(_arg1,_arg2,_arg3);// method pointer signature
};

Крім того, нам потрібен допоміжний метод mem_fun3 для вищевказаного класу для допомоги у виклику.

template<typename _Ret,typename _Class,typename _arg1,typename _arg2,typename _arg3>
mem_fun3_t<_Ret,_Class,_arg1,_arg2,_arg3> mem_fun3 ( _Ret (_Class::*_Pm)          (_arg1,_arg2,_arg3) )
{
  return (mem_fun3_t<_Ret,_Class,_arg1,_arg2,_arg3>(_Pm));

}

Тепер, щоб прив’язати параметри, треба записати функцію зв’язування. Отже, ось що:

template<typename _Func,typename _Ptr,typename _arg1,typename _arg2,typename _arg3>
class binder3
{
public:
//This is the constructor that does the binding part
binder3(_Func fn,_Ptr ptr,_arg1 i,_arg2 j,_arg3 k)
    :m_ptr(ptr),m_fn(fn),m1(i),m2(j),m3(k){}

 //and this is the function object 
 void operator()() const
 {
        m_fn(m_ptr,m1,m2,m3);//that calls the operator
    }
private:
    _Ptr m_ptr;
    _Func m_fn;
    _arg1 m1; _arg2 m2; _arg3 m3;
};

І, допоміжна функція для використання класу binder3 - bind3:

//a helper function to call binder3
template <typename _Func, typename _P1,typename _arg1,typename _arg2,typename _arg3>
binder3<_Func, _P1, _arg1, _arg2, _arg3> bind3(_Func func, _P1 p1,_arg1 i,_arg2 j,_arg3 k)
{
    return binder3<_Func, _P1, _arg1, _arg2, _arg3> (func, p1,i,j,k);
}

Тепер ми повинні використовувати це з класом Command; використовувати наступний типdef:

typedef binder3<mem_fun3_t<int,T,int,int,int> ,T* ,int,int,int> F3;
//and change the signature of the ctor
//just to illustrate the usage with a method signature taking more than one parameter
explicit Command(T* pObj,F3* p_method,long timeout,const char* key,
long priority = PRIO_NORMAL ):
m_objptr(pObj),m_timeout(timeout),m_key(key),m_value(priority),method1(0),method0(0),
method(0)
{
    method3 = p_method;
}

Ось як ви це називаєте:

F3 f3 = PluginThreadPool::bind3( PluginThreadPool::mem_fun3( 
      &CTask::ThreeParameterTask), task1,2122,23 );

Примітка: f3 (); викличе метод task1-> ThreeParameterTask (21,22,23) ;.

Повний контекст цієї моделі за наступним посиланням


2

Великою перевагою реалізації функцій функціонерів є те, що вони можуть підтримувати та повторно використовувати стан між дзвінками. Наприклад, багато алгоритмів динамічного програмування, як алгоритм Вагнера-Фішера для обчислення відстані Левенштейна між рядками, працюють, заповнивши велику таблицю результатів. Дуже неефективно розподіляти цю таблицю щоразу, коли функція викликається, тому реалізація функції як функтора та перетворення таблиці на змінну члена може значно підвищити продуктивність.

Нижче наводиться приклад реалізації алгоритму Вагнера-Фішера як функтора. Зауважте, як таблиця розподіляється в конструкторі, а потім повторно використовується operator(), змінюючи розмір за необхідності.

#include <string>
#include <vector>
#include <algorithm>

template <typename T>
T min3(const T& a, const T& b, const T& c)
{
   return std::min(std::min(a, b), c);
}

class levenshtein_distance 
{
    mutable std::vector<std::vector<unsigned int> > matrix_;

public:
    explicit levenshtein_distance(size_t initial_size = 8)
        : matrix_(initial_size, std::vector<unsigned int>(initial_size))
    {
    }

    unsigned int operator()(const std::string& s, const std::string& t) const
    {
        const size_t m = s.size();
        const size_t n = t.size();
        // The distance between a string and the empty string is the string's length
        if (m == 0) {
            return n;
        }
        if (n == 0) {
            return m;
        }
        // Size the matrix as necessary
        if (matrix_.size() < m + 1) {
            matrix_.resize(m + 1, matrix_[0]);
        }
        if (matrix_[0].size() < n + 1) {
            for (auto& mat : matrix_) {
                mat.resize(n + 1);
            }
        }
        // The top row and left column are prefixes that can be reached by
        // insertions and deletions alone
        unsigned int i, j;
        for (i = 1;  i <= m; ++i) {
            matrix_[i][0] = i;
        }
        for (j = 1; j <= n; ++j) {
            matrix_[0][j] = j;
        }
        // Fill in the rest of the matrix
        for (j = 1; j <= n; ++j) {
            for (i = 1; i <= m; ++i) {
                unsigned int substitution_cost = s[i - 1] == t[j - 1] ? 0 : 1;
                matrix_[i][j] =
                    min3(matrix_[i - 1][j] + 1,                 // Deletion
                    matrix_[i][j - 1] + 1,                      // Insertion
                    matrix_[i - 1][j - 1] + substitution_cost); // Substitution
            }
        }
        return matrix_[m][n];
    }
};

1

Функтор може також використовуватися для імітації визначення локальної функції в межах функції. Зверніться до питання та іншого .

Але місцевий функціонер не може отримати доступ до зовнішніх змінних. Функція лямбда (C ++ 11) є кращим рішенням.


-10

Я "виявив" дуже цікаве використання функторів: я використовую їх, коли не маю гарного імені для одного методу, як функтор - це метод без імені ;-)


Чому ви описуєте функтора як "метод без імені"?
Андерсон Грін

5
Функція без виходу імені називається лямбда.
Пол Фульц II
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.