Каково практическое использование указателей на функции-члены?

Я прочитал эту статью и понял, что когда вы хотите вызвать указатель на функцию-член, вам понадобится экземпляр (либо указатель на него, либо ссылка на стек) и назовите его так:

(instance.*mem_func_ptr)(..)
or
(instance->*mem_func_ptr)(..)

Мой вопрос основан на следующем: поскольку у вас есть экземпляр, почему бы не вызвать функцию-член напрямую, например:

instance.mem_func(..) //or: instance->mem_func(..)

Каково рациональное / практическое использование указателей на функции-члены?

[редактировать]

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

Не совсем так: я обнаружил, что могу сделать то же самое более ясным и аккуратным способом, просто используя виртуальный базовый класс. Нет необходимости в указателях на функции-члены. Именно при разработке вышеизложенного возникло сомнение в практическом использовании / значении указателей на функции-члены.

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

[edit - @sbi и другие]

Вот пример программы, чтобы проиллюстрировать мою точку зрения: (обратите внимание на Handle_THREE ())

#include <iostream>
#include <string>
#include <map>


//-----------------------------------------------------------------------------
class Base
{
public:
    ~Base() {}
    virtual void Handler(std::string sItem) = 0;
};

//-----------------------------------------------------------------------------
typedef void (Base::*memfunc)(std::string);

//-----------------------------------------------------------------------------
class Paper : public Base
{
public:
    Paper() {}
    ~Paper() {}
    virtual void Handler(std::string sItem) { std::cout << "Handling paper\n"; }
};

//-----------------------------------------------------------------------------
class Wood : public Base
{
public:
    Wood() {}
    ~Wood() {}
    virtual void Handler(std::string sItem) { std::cout << "Handling wood\n"; }
};


//-----------------------------------------------------------------------------
class Glass : public Base
{
public:
    Glass() {}
    ~Glass() {}
    virtual void Handler(std::string sItem) { std::cout << "Handling glass\n"; }
};

//-----------------------------------------------------------------------------
std::map< std::string, memfunc > handlers;
void AddHandler(std::string sItem, memfunc f) { handlers[sItem] = f; }

//-----------------------------------------------------------------------------
std::map< Base*, memfunc > available_ONE;
void AddAvailable_ONE(Base *p, memfunc f) { available_ONE[p] = f; }

//-----------------------------------------------------------------------------
std::map< std::string, Base* > available_TWO;
void AddAvailable_TWO(std::string sItem, Base *p) { available_TWO[sItem] = p; }

//-----------------------------------------------------------------------------
void Handle_ONE(std::string sItem)
{
    memfunc f = handlers[sItem];
    if (f)
    {
        std::map< Base*, memfunc >::iterator it;
        Base *inst = NULL;
        for (it=available_ONE.begin(); ((it != available_ONE.end()) && (inst==NULL)); it++)
        {
            if (it->second == f) inst = it->first;
        }
        if (inst) (inst->*f)(sItem);
        else std::cout << "No instance of handler for: " << sItem << "\n";
    }
    else std::cout << "No handler for: " << sItem << "\n";
}

//-----------------------------------------------------------------------------
void Handle_TWO(std::string sItem)
{
    memfunc f = handlers[sItem];
    if (f)
    {
        Base *inst = available_TWO[sItem];
        if (inst) (inst->*f)(sItem);
        else std::cout << "No instance of handler for: " << sItem << "\n";
    }
    else std::cout << "No handler for: " << sItem << "\n";
}

//-----------------------------------------------------------------------------
void Handle_THREE(std::string sItem)
{
    Base *inst = available_TWO[sItem];
    if (inst) inst->Handler(sItem);
    else std::cout << "No handler for: " << sItem << "\n";
}


//-----------------------------------------------------------------------------
int main()
{
    Paper p;
    Wood w;
    Glass g;


    AddHandler("Paper", (memfunc)(&Paper::Handler));
    AddHandler("Wood", (memfunc)(&Wood::Handler));
    AddHandler("Glass", (memfunc)(&Glass::Handler));

    AddAvailable_ONE(&p, (memfunc)(&Paper::Handler));
    AddAvailable_ONE(&g, (memfunc)(&Glass::Handler));

    AddAvailable_TWO("Paper", &p);
    AddAvailable_TWO("Glass", &g);

    std::cout << "\nONE: (bug due to member-function address being relative to instance address)\n";
    Handle_ONE("Paper");
    Handle_ONE("Wood");
    Handle_ONE("Glass");
    Handle_ONE("Iron");

    std::cout << "\nTWO:\n";
    Handle_TWO("Paper");
    Handle_TWO("Wood");
    Handle_TWO("Glass");
    Handle_TWO("Iron");

    std::cout << "\nTHREE:\n";
    Handle_THREE("Paper");
    Handle_THREE("Wood");
    Handle_THREE("Glass");
    Handle_THREE("Iron");
}

{edit] Возможная проблема с прямым вызовом в приведенном выше примере:
В Handler_THREE () имя метода должно быть жестко закодировано, чтобы изменения вносились везде, где он используется, применить какие-либо изменения к методу. При использовании указателя на функцию-член единственное дополнительное изменение, которое необходимо сделать, - это место создания указателя.

[править] Практическое использование, почерпнутое из ответов:

Из ответ Chubsdad:
Что: для вызова mem-func-ptr используется специальная функция« Caller »;
Преимущество: для защиты кода с помощью функций, предоставляемых другими объектами.
Как: если определенные функции используются во многих местах и ​​имя и / или параметры меняются, вам нужно только измените имя, в котором он размещен как указатель, и адаптируйте вызов в функции «Caller». (Если функция используется как instance.function (), ее нужно везде менять.)

Из ответ Мэтью Флашена:
Что: локальная специализация в классе
Преимущество: делает код более ясным, простым и легким в использовании и обслуживании
Как: заменяет код, который обычно реализуется с использованием сложной логики, на ( потенциально) большие операторы switch () / if-then с прямыми указателями на специализацию; довольно похожа на функцию "Caller" выше.


person slashmais    schedule 18.10.2010    source источник
comment
Все дело в том, что mem_func не обязательно статичен, поэтому мы используем mem_func_ptr. Тот факт, что вам нужен экземпляр, не имеет ничего общего с указателями на функции или их использованием. Вы ни в чем не сбиваетесь с пути. Если вы понимаете, для чего нужны указатели на обычные функции, вы понимаете, для чего нужны указатели на функции-члены. Точно то же самое. Итак: вы понимаете, для чего нужны обычные указатели на функции?   -  person GManNickG    schedule 18.10.2010
comment
@GMan: ты понял мой вопрос?   -  person slashmais    schedule 18.10.2010
comment
@slashmais: Да ... Я дам вам, что обычно не так полезно, но все равно используется то же самое.   -  person GManNickG    schedule 18.10.2010
comment
Я согласен, они не распространены, но полезны, когда они вам нужны. Также, что касается @ slashmais, он похож на указатели на функции, но для методов-членов. Думаю, я использовал их для реализации конечных автоматов на C ++ один или два раза.   -  person kenny    schedule 19.10.2010
comment
a.*b(c) это a.*(b(c)), не то, что вы имели в виду: (a.*b)(c). Аналогично ->*.   -  person    schedule 19.10.2010
comment
@Roger Pate: Починим сейчас, спасибо   -  person slashmais    schedule 19.10.2010


Ответы (12)


Есть много практических применений. Мне на ум приходит следующее:

Предположим, что основная функция, такая как ниже (соответственно определенные myfoo и MFN)

void dosomething(myfoo &m, MFN f){   // m could also be passed by reference to 
                                     // const
   m.*f();
}

Такая функция при наличии указателя на функции-члены становится открытой для расширения и закрытой для модификации (OCP < / а>)

Также обратитесь к идиоме Safe bool, которая разумно использует указатель на элементы.

person Chubsdad    schedule 18.10.2010
comment
Чтобы вызвать его, вам нужна ссылка на экземпляр, но тогда вы можете вызвать функцию напрямую, и вам не нужен указатель на нее. - person slashmais; 18.10.2010
comment
Ключом к достижению OCP является отделение от фактического имени функции в основной функции. - person Chubsdad; 18.10.2010
comment
@slashmais: что, если вы переименуете функцию-член во что-нибудь другое. Если вы привяжете к имени, эта основная функция изменится. Но, как я показал, имя функции значения не имеет. Он закрыт для модификации и открыт для расширения - person Chubsdad; 19.10.2010
comment
Я понимаю, что вы говорите. Преимущество вашего пути будет заключаться в том, что конкретная функция используется во многих местах, тогда вам нужно только изменить имя, в котором она выделена как указатель; если функция используется как instance.function (), то ее нужно везде менять. Хорошо, это по крайней мере одно разумное использование (+1). - person slashmais; 19.10.2010
comment
@slashmais: Да, это сердце OCP. Основная функция закрыта для модификации и открыта для расширения. - person Chubsdad; 19.10.2010
comment
Принял этот ответ, потому что он единственный, который показывает реальное и практическое использование указателей на функции-члены. - person slashmais; 20.10.2010

По той же причине, по которой вы используете любой указатель на функцию: вы можете использовать произвольную программную логику для установки переменной указателя на функцию перед ее вызовом. Вы можете использовать переключатель, if / else, передать его в функцию, что угодно.

РЕДАКТИРОВАТЬ:

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

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

#include <iostream>

class MemberDemo;
typedef void (MemberDemo::*MemberDemoPtr)();

class MemberDemo
{
    public:
    void test1();
    void test2();

    private:
    void test1_internal();
    void test2_internal();
    void do_with_setup_teardown(MemberDemoPtr p);
};

void MemberDemo::test1()
{
    do_with_setup_teardown(&MemberDemo::test1_internal);
}

void MemberDemo::test2()
{
    do_with_setup_teardown(&MemberDemo::test2_internal);
}

void MemberDemo::test1_internal()
{
    std::cout << "Test1" << std::endl;
}

void MemberDemo::test2_internal()
{
    std::cout << "Test2" << std::endl;
}

void MemberDemo::do_with_setup_teardown(MemberDemoPtr mem_ptr)
{
    std::cout << "Setup" << std::endl;
    (this->*mem_ptr)();
    std::cout << "Teardown" << std::endl;
}

int main()
{
    MemberDemo m;
    m.test1();
    m.test2();
}
person Matthew Flaschen    schedule 18.10.2010
comment
В статье, на которую я ссылаюсь, автор показывает, что фактическое использование указателей функций-членов несколько неясно, см. Раздел, озаглавленный: Использование указателей функций-членов - person slashmais; 18.10.2010
comment
+: Если вы хотите вызвать функцию-член-ptr, вам понадобится экземпляр-класс, но тогда вы также можете напрямую вызвать функцию-член. - person slashmais; 18.10.2010
comment
@slash, он говорит, что он используется только для надуманных примеров и делегатов. Даже если это правда, в статье не оспаривается, что делегаты полезны в самых разных приложениях. - person Matthew Flaschen; 18.10.2010
comment
@slash, опять же, вы всегда можете вызвать бесплатную функцию напрямую. Принципы работы указателей на обычные функции и указатели на функции-члены на самом деле не сильно отличаются. Обычный указатель на функцию может быть вызван с параметрами (при условии правильного типа). Вы можете рассматривать экземпляр как просто дополнительный параметр (иногда называемый неявным параметром). - person Matthew Flaschen; 18.10.2010
comment
Я согласен с Павлом, не думаю, что этот ответ имеет особую ценность. Очевидное продолжение: зачем мне это делать и как использовать указатель на функцию-член? - person Omnifarious; 18.10.2010
comment
@Omni, @Pavel, я добавил пример. Это нереально, но призвано продемонстрировать случай, когда виртуальные функции, вероятно, не имеют смысла. - person Matthew Flaschen; 19.10.2010
comment
@Mathew - Это делает ваш ответ намного лучше. - person Omnifarious; 19.10.2010
comment
Да, ваш пример демонстрирует второй случай, когда будут полезны mem-ptrs (/ обязательны) (см. Ответ Чубсдада для другого). То, что я беру из вашего примера, - это средство для локальной специализации в классе, для которого в противном случае потребовались бы большие операторы switch () / if-then, плюс (большой плюс), это делает код намного яснее и проще. - person slashmais; 19.10.2010

Мой вопрос основан на следующем: если у вас есть экземпляр, почему бы не вызвать функцию-член напрямую [?]

Предварительно: за более чем 15 лет программирования на C ++ я использовал указатели на элементы, может быть, дважды или трижды. При наличии виртуальных функций от этого мало пользы.

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

person sbi    schedule 18.10.2010
comment
Ваш пример подчеркивает мою точку зрения: когда итератор предоставляет Elem *, у вас есть экземпляр и теперь вы можете вызывать метод напрямую - нет необходимости в member-ptr. - person slashmais; 18.10.2010
comment
@slashmark: Прочтите еще раз, я даже выделил важную часть! Указатели на функции-члены необходимы, если вы хотите решить, какую функцию-член вызывать , прежде чем вы сможете решить, какой объект ее вызывать. В примере, который я связал, решение о функции принимается вне std::for_each(), решение об объектах внутри него. - person sbi; 18.10.2010
comment
@slash, во-первых, экземпляр является указателем на Selector, а не на Elem. Во-вторых, for_each определенно не будет выполнять что-то вроде instance->prepare_elem(elem); напрямую, поскольку он знает только, как выполнить UnaryFunction. Чтобы получить UnaryFunction, вы можете сначала передать указатель на функцию-член в mem_fun, а затем передать результат в bind1st. - person Matthew Flaschen; 18.10.2010
comment
Я никогда не использовал их напрямую, но я часто использовал их через ::boost::function или ::std::tr1::function. - person Omnifarious; 18.10.2010
comment
Я добавил к своему вопросу пример программы, чтобы показать, что я имею в виду. - person slashmais; 18.10.2010

Я считаю, что настоящая полезность указателей на функции-члены проявляется, когда вы смотрите на конструкцию более высокого уровня, такую ​​как boost::bind(). Это позволит вам обернуть вызов функции как объект, который позже может быть привязан к конкретному экземпляру объекта, а затем передан как копируемый объект. Это действительно мощная идиома, позволяющая использовать отложенные обратные вызовы, делегаты и сложные операции с предикатами. См. Несколько примеров в моем предыдущем посте:

https://stackoverflow.com/questions/1596139/hidden-features-and-dark-corners-of-stl/1596626#1596626

person the_mandrill    schedule 18.10.2010
comment
Это используется для этого, но в этот момент вы все еще можете вызывать член напрямую - указатель не нужен; виртуальный базовый класс позволит то же самое. - person slashmais; 18.10.2010
comment
Полезность заключается в том, что вызывающему коду не нужно знать, какая функция должна быть вызвана, потому что вы передаете функцию в качестве параметра, например, см. Мой предыдущий пример с использованием find_if(). - person the_mandrill; 18.10.2010

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

Одно из распространенных применений - алгоритмы. В std :: for_each мы можем захотеть вызвать функцию-член класса каждого члена нашей коллекции. Мы также можем захотеть вызвать функцию-член нашего собственного класса для каждого члена коллекции - для последнего требуется boost :: bind, первое может быть выполнено с помощью семейства классов STL mem_fun (если у нас нет коллекция shared_ptr, и в этом случае нам также нужно усилить :: bind). Мы также могли бы использовать функцию-член в качестве предиката в определенных алгоритмах поиска или сортировки. (Это избавляет нас от необходимости писать собственный класс, который перегружает operator () для вызова члена нашего класса, мы просто передаем его напрямую в boost :: bind).

Другое использование, как я уже упоминал, - это обратные вызовы, часто в коде, управляемом событиями. Когда операция завершена, нам нужен метод нашего класса, вызываемый для обработки завершения. Часто это можно обернуть в функтор boost :: bind. В этом случае мы должны быть очень осторожны, чтобы правильно управлять временем жизни этих объектов и их поточной безопасностью (тем более, что отладка может быть очень сложной, если что-то пойдет не так). Тем не менее, это еще раз может избавить нас от написания большого количества кода-оболочки.

person CashCow    schedule 18.10.2010

Лучшее использование указателей на функции-члены - разорвать зависимости.

Хорошим примером, когда требуется указатель на функцию-член, является шаблон подписчика / издателя:

http://en.wikipedia.org/wiki/Publish/subscribe

person BЈовић    schedule 18.10.2010

На мой взгляд, указатели на функции-члены не очень полезны для среднего программиста в их необработанном виде. OTOH, конструкции типа ::std::tr1::function, которые объединяют указатели функций-членов вместе с указателем на объект, над которым они должны работать, чрезвычайно полезны.

Конечно ::std::tr1::function очень сложно. Итак, я приведу вам простой пример, который вы бы фактически не использовали на практике, если бы у вас было ::std::tr1::function в наличии:

// Button.hpp
#include <memory>

class Button {
 public:
   Button(/* stuff */) : hdlr_(0), myhandler_(false) { }
   ~Button() {
      // stuff
      if (myhandler_) {
         delete hdlr_;
      }
   }
   class PressedHandler {
    public:
      virtual ~PressedHandler() = 0;

      virtual void buttonPushed(Button *button) = 0;
   };

   // ... lots of stuff

   // This stores a pointer to the handler, but will not manage the
   // storage.  You are responsible for making sure the handler stays
   // around as long as the Button object.
   void setHandler(const PressedHandler &hdlr) {
      hdlr_ = &hdlr;
      myhandler_ = false;
   }

   // This stores a pointer to an object that Button does not manage.  You
   // are responsible for making sure this object stays around until Button
   // goes away.
   template <class T>
   inline void setHandlerFunc(T &dest, void (T::*pushed)(Button *));

 private:
   const PressedHandler *hdlr_;
   bool myhandler_;

   template <class T>
   class PressedHandlerT : public Button::PressedHandler {
    public:
      typedef void (T::*hdlrfuncptr_t)(Button *);

      PressedHandlerT(T *ob, hdlrfuncptr_t hdlr) : ob_(ob), func_(hdlr) { }
      virtual ~PressedHandlerT() {}

      virtual void buttonPushed(Button *button) { (ob_->*func_)(button); }

    private:
      T * const ob_;
      const hdlrfuncptr_t func_;
   };
};

template <class T>
inline void Button::setHandlerFunc(T &dest, void (T::*pushed)(Button *))
{
   PressedHandler *newhandler = new PressedHandlerT<T>(&dest, pushed);
   if (myhandler_) {
      delete hdlr_;
   }
   hdlr_ = newhandler;
   myhandler_ = true;
}

// UseButton.cpp
#include "Button.hpp"
#include <memory>

class NoiseMaker {
 public:
   NoiseMaker();
   void squee(Button *b);
   void hiss(Button *b);
   void boo(Button *b);

 private:
   typedef ::std::auto_ptr<Button> buttonptr_t;
   const buttonptr_t squeebutton_, hissbutton_, boobutton_;
};


NoiseMaker::NoiseMaker()
     : squeebutton_(new Button), hissbutton_(new Button), boobutton_(new Button)
{
   squeebutton_->setHandlerFunc(*this, &NoiseMaker::squee);
   hissbutton_->setHandlerFunc(*this, &NoiseMaker::hiss);
   boobutton_->setHandlerFunc(*this, &NoiseMaker::boo);
}

Предполагая, что Button находится в библиотеке и не может быть изменен вами, мне было бы приятно увидеть, как вы реализуете это чисто с использованием виртуального базового класса, не прибегая к каким-либо конструкциям switch или if else if.

person Omnifarious    schedule 18.10.2010
comment
Ваш код действительно дал мне несколько идей для моей игры с X-dev. Я обязательно посмотрю на ваш образец кода еще раз. - person slashmais; 19.10.2010
comment
Я не знал о std :: tr1 - thanx. Похоже, с этим можно многое сделать :) - person slashmais; 19.10.2010

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

object.method();
pointer->method();

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

Чтобы лучше проиллюстрировать это, позвольте мне провести следующую простую аналогию. Допустим, у вас есть массив

int a[100];

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

a[5]; a[8]; a[23];

В этом случае конкретные индексы жестко запрограммированы в вашу программу. Но вы также можете получить доступ к элементам массива с помощью индекса времени выполнения - целочисленной переменной i

a[i];

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

Вопрос, который вы задаете («поскольку у вас есть экземпляр, почему бы не вызвать функцию-член напрямую»), можно перевести в контекст этого массива. Вы в основном спрашиваете: «Зачем нам нужен доступ к переменному индексу a[i], когда у нас есть прямой доступ к константе времени компиляции, такой как a[1] и a[3]?» Надеюсь, вы знаете ответ на этот вопрос и понимаете ценность выбора конкретного элемента массива во время выполнения.

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

person AnT    schedule 19.10.2010

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

Не то, чем вы пользуетесь каждый день ...

person Community    schedule 18.10.2010
comment
В том, что вы предлагаете, есть заслуга, но все же ... Что касается X-dev, которым я занят: связывание события, виджета, родительского окна и метода для вызова в этом родительском окне - возможное место для member-fp's. - person slashmais; 19.10.2010

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

Вы можете использовать гигантский оператор if / else if
Вы можете использовать оператор switch
Или вы можете использовать таблицу указателей функций (таблица переходов)

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

Однако все зависит от личных предпочтений. Оператор переключения и таблица переходов в любом случае соответствуют более или менее одному и тому же скомпилированному коду :)

person Goz    schedule 18.10.2010
comment
Я хочу сказать, что вам нужен экземпляр - если у вас есть экземпляр, зачем вам нужен указатель func? - person slashmais; 18.10.2010
comment
Я даю вам пример того, когда набор указателей на функции может быть полезным ... - person Goz; 18.10.2010
comment
Но да ... вам редко НУЖЕН указатель func. Обычно есть несколько способов снять шкуру с кошки ... - person Goz; 18.10.2010

Указатели участников + шаблоны = чистая победа.

например Как сообщить, содержит ли класс определенную функцию-член во время компиляции

or

template<typename TContainer,
         typename TProperty,
         typename TElement = decltype(*Container().begin())>
TProperty grand_total(TContainer& items, TProperty (TElement::*property)() const)
{
   TProperty accum = 0;
   for( auto it = items.begin(), end = items.end(); it != end; ++it) {
       accum += (it->*property)();
   }
   return accum;
}

auto ship_count = grand_total(invoice->lineItems, &LineItem::get_quantity);
auto sub_total = grand_total(invoice->lineItems, &LineItem::get_extended_total);
auto sales_tax = grand_total(invoice->lineItems, &LineItem::calculate_tax);
person Ben Voigt    schedule 19.10.2010
comment
Эта концепция может быть очень полезной. - person slashmais; 20.10.2010

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

Это полностью упускает из виду суть. Здесь есть две независимые проблемы:

  • какое действие предпринять позже
  • на каком объекте выполнить это действие

Наличие ссылки на экземпляр удовлетворяет второму требованию. Указатели на функции-члены обращаются к первому: они представляют собой очень прямой способ записать - в какой-то момент выполнения программы - какое действие следует предпринять на более позднем этапе выполнения, возможно, другой частью программы.

ПРИМЕР

Допустим, у вас есть обезьяна, которая может целовать или щекотать людей. В 18:00 ваша программа должна освободить обезьяну и знать, кого обезьяна должна посетить, но около 15:00 ваш пользователь напечатает, какое действие следует предпринять.

Подход новичка

Итак, в 15:00 вы можете установить переменную "enum Action {Kiss, Tickle} action;", затем в 18:00 вы можете сделать что-то вроде "if (action == Kiss) monkey-> kiss (person); else monkey-> tickle (человек)".

Проблемы

Но это вводит дополнительный уровень кодирования (для поддержки этого введен тип Action - встроенные типы могут использоваться, но будут более подвержены ошибкам и менее значимы по своей сути). Затем - после определения того, какое действие следует предпринять в 15:00, в 18:00 вы должны повторно проконсультироваться с этим закодированным значением, чтобы решить, какое действие предпринять, для чего потребуется другое if / else или переключиться на закодированное значение. Все это неуклюже, многословно, медленно и подвержено ошибкам.

Указатели на функции-члены

Лучше использовать более специализированный варибал - указатель на функцию-член, который напрямую записывает, какое действие нужно выполнить в 18:00. Вот что такое указатель на функцию-член. Это установленный ранее селектор поцелуев или щекоток, создающий «состояние» для обезьяны - щекотка это или поцелуй, - которое можно использовать позже. Более поздний код просто вызывает любую заданную функцию, не задумываясь о ее возможностях и не имея каких-либо операторов if / else-if или switch.

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

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

person Tony Delroy    schedule 18.10.2010