Каково практическое использование указателей на функции-члены?
Я прочитал эту статью, и я беру с нее то, что, когда вы хотите вызвать указатель на функцию-член, вам понадобится экземпляр (указатель на один или ссылку на стек) и назовите его так:
(instance.*mem_func_ptr)(..)
or
(instance->*mem_func_ptr)(..)
Мой вопрос основан на этом: поскольку у вас есть экземпляр, почему бы не вызвать функцию-член напрямую, например:
instance.mem_func(..) //or: instance->mem_func(..)
Какое рациональное/практическое использование указателей на функции-члены?
[править]
Я играю с X-разработкой и достиг стадии, когда я реализую виджеты; поток событий-цикла для перевода 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:
Что: выделенная функция "Caller" используется для вызова mem-func-ptr;
Преимущество: защищать код с помощью функций, предоставляемых другими объектами.
Как: Если конкретная функция (s ) используются во многих местах, а имя и/или параметры изменяются, тогда вам нужно только изменить имя, в котором оно выделено в качестве указателя, и адаптировать вызов в функции "Caller" . (Если функция используется как instance.function(), она должна быть изменена повсюду.)
Из ответ Мэтью Флашен:
Что: Локальная специализация в классе
Преимущества: Делает код намного понятнее, проще и проще в использовании и поддержке
Как: Заменяет код, который обычно реализуется с использованием сложной логики с (потенциально) большим переключателем()/if-then с прямыми указателями на специализацию; аналогично функции "Caller" выше.
Ответы
Ответ 1
Существует много практических применений. Тот, который приходит мне на ум, выглядит следующим образом:
Предположим, что основная функция, такая как ниже (подходящим образом определена myfoo и MFN)
void dosomething(myfoo &m, MFN f){ // m could also be passed by reference to
// const
m.*f();
}
Такая функция при наличии указателя на функции-член становится открытой для расширения и закрыта для модификации (OCP)
Также ссылайтесь на безопасную идиому bool, которая использует графический указатель на элементы.
Ответ 2
По той же причине вы используете любой указатель функции: вы можете использовать произвольную программную логику для установки переменной указателя функции перед ее вызовом. Вы можете использовать переключатель, if/else, передать его в функцию, что угодно.
EDIT:
Пример в вопросе показывает, что иногда вы можете использовать виртуальные функции в качестве альтернативы указателям на функции-члены. Это не должно удивлять, потому что в программировании обычно есть несколько подходов.
Вот пример случая, когда виртуальные функции, вероятно, не имеют смысла. Как и код в 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();
}
Ответ 3
Мой вопрос основан на этом: поскольку у вас есть экземпляр, почему бы не вызвать функцию-член напрямую [?]
Upfront: более 15 лет программирования на С++ я использовал указатели элементов, возможно, дважды или трижды. Когда виртуальные функции находятся вокруг, для этого не все так много.
Вы использовали бы их, если хотите вызвать определенные функции-члены для объекта (или многих объектов), и вам нужно решить, какую функцию-член вызывать , прежде чем вы сможете узнать, для какого объекта (-ов) вызывать это на. Здесь - пример того, кто хочет это сделать.
Ответ 4
Я нахожу, что реальная полезность указателей на функции-член возникает, когда вы смотрите на конструкцию более высокого уровня, такую как boost::bind()
. Это позволит вам обернуть вызов функции как объект, который позже может быть привязан к конкретному экземпляру объекта, а затем передан как скопируемый объект. Это действительно мощная идиома, которая позволяет отложенные обратные вызовы, делегаты и сложные операции предикатов. См. Мои предыдущие сообщения для некоторых примеров:
https://stackoverflow.com/questions/1596139/hidden-features-and-dark-corners-of-stl/1596626#1596626
Ответ 5
Функции-члены, как и многие указатели на функции, действуют как обратные вызовы. Без них можно было бы создать какой-то абстрактный класс, который вызывает ваш метод, но это может быть большой дополнительной работой.
Одним из распространенных применений является алгоритм. В std:: for_each мы можем вызвать функцию-член класса каждого члена нашей коллекции. Мы также можем вызвать функцию-член нашего собственного класса для каждого члена коллекции - для последнего требуется boost:: bind для достижения, первое может быть выполнено с семейством классов STL mem_fun (если у нас нет коллекция shared_ptr, и в этом случае нам нужно также увеличить:: bind в этом случае). Мы могли бы также использовать функцию-член как предикат в определенных алгоритмах поиска или сортировки. (Это устраняет необходимость писать пользовательский класс, который перегружает operator() для вызова члена нашего класса, мы просто передаем его непосредственно для boost:: bind).
Другое использование, как я уже упоминал, - это обратные вызовы, часто в коде, управляемом событиями. Когда операция завершена, мы хотим, чтобы метод нашего класса вызывал обработку завершения. Это часто можно обернуть в функцию boost:: bind. В этом случае мы должны быть очень осторожны, чтобы правильно управлять временем жизни этих объектов и их безопасностью потоков (особенно, поскольку это может быть очень сложно отладить, если что-то пойдет не так). Тем не менее, это еще раз может спасти нас от написания большого количества "оберточного" кода.
Ответ 6
Лучшее использование указателей для функций-членов - это разбить зависимости.
Хороший пример, где требуется указатель на функцию-член, - это шаблон подписчика/издателя:
http://en.wikipedia.org/wiki/Publish/subscribe
Ответ 7
На мой взгляд, указатели функций-членов не очень полезны среднему программисту в их исходной форме. 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
где-то.
Ответ 8
Вся точка указателей типа функции указателя на элемент состоит в том, что они действуют как способ выполнения определенного метода. Когда вы используете "обычный" синтаксис для доступа к методу
object.method();
pointer->method();
Часть method
- это фиксированная спецификация времени компиляции метода, который вы хотите вызвать. Он жестко закодирован в вашу программу. Он никогда не изменится. Но, используя указатель типа функции указатель-член, вы можете заменить эту фиксированную часть переменной, изменяемой во время выполнения метода.
Чтобы лучше проиллюстрировать это, позвольте мне сделать следующую простую аналогию. Скажем, у вас есть массив
int a[100];
Вы можете получить доступ к своим элементам с фиксированным индексом времени компиляции
a[5]; a[8]; a[23];
В этом случае конкретные индексы жестко закодированы в вашу программу. Но вы также можете получить доступ к элементам массива с индексом времени выполнения - целочисленной переменной i
a[i];
значение i
не является фиксированным, оно может меняться во время выполнения, что позволяет вам выбирать различные элементы массива во время выполнения. Это очень похоже на то, что позволяют указывать типы функций указателя на член.
Вопрос, который вы задаете ( "поскольку у вас есть экземпляр, почему бы не вызвать функцию-член напрямую" ), можно преобразовать в этот контекст массива. Вы в основном задаете вопрос: "Зачем нам нужен доступ к переменному индексу a[i]
, когда у нас есть прямой постоянный доступ к компиляции, такой как a[1]
и a[3]
?" Надеюсь, вы знаете ответ на этот вопрос и осознаете ценность выбора времени выполнения определенного элемента массива.
То же самое относится к указателям типа функции "указатель-член": они снова позволяют выполнять выбор времени выполнения определенного класса.
Ответ 9
Вариант использования состоит в том, что у вас есть несколько методов-членов с одной и той же сигнатурой, и вы хотите построить логику, которую нужно вызывать при данных обстоятельствах. Это может быть полезно для реализации алгоритмов конечных автоматов.
Не то, что вы используете каждый день...
Ответ 10
Представьте себе, что у вас есть функция, которая может вызвать одну из нескольких функций в зависимости от переданных параметров.
Вы можете использовать гигантский оператор if/else if
Вы можете использовать оператор switch
Или вы можете использовать таблицу указателей функций (таблицу переходов)
Если у вас много разных опций, таблица перехода может быть намного более чистым способом организации вашего кода...
Однако это относится к личным предпочтениям. Оператор switch и таблица перехода соответствуют более или менее одному и тому же скомпилированному коду:)
Ответ 11
Пользовательские указатели + шаблоны = чистый выигрыш.
например. Как сказать, если класс содержит определенную функцию-член во время компиляции
или
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);
Ответ 12
В этой статье описаны некоторые сценарии, в которых полезны указатели на функции-члены.
Ответ 13
Чтобы вызвать его, вам нужна ссылка на экземпляр, но тогда вы можете вызвать func direct и не нуждаться в указателе на него.
Это полностью отсутствует. Здесь есть две независимые проблемы:
- какое действие нужно предпринять в какой-то более поздний момент времени
- какой объект выполнить это действие на
Наличие ссылки на экземпляр удовлетворяет второму требованию. Указатели на функции-члены обращаются к первому: они являются очень прямым способом записи - в какой-то момент выполнения программы - какое действие следует предпринять на каком-то более позднем этапе выполнения, возможно, другой частью программы.
Пример
Скажите, что у вас есть обезьяна, которая может целовать людей или щекотать их. В 18:00 ваша программа должна отключить обезьяну и знает, кому должна побывать обезьяна, но около 3 часов ваш пользователь набирает, какие действия следует предпринять.
Подход новичков
Итак, в 3 часа дня вы можете установить переменную "действие enum Action {Kiss, Tickle}", затем в 18:00 вы можете сделать что-то вроде "if (action == Kiss) обезьяна- > поцелуй (человек), а другая обезьяна -. > щекотка (человек)"
Проблемы
Но это введение дополнительного уровня кодирования (тип действия, введенный для поддержки этого - встроенные типы, может использоваться, но будет более подвержен ошибкам и менее насущно значимым). Затем - после того, как вы определили, какие действия следует предпринять в 3 часа дня, в 6 вечера вам придется излишне проконсультироваться с этим закодированным значением, чтобы решить, какое действие предпринять, для чего потребуется другое if/else или переключить на закодированное значение. Все это неуклюже, многословно, медленно и подвержено ошибкам.
Указатели функций функций
Лучше всего использовать более специализированный varibale - указатель на функцию-член, который напрямую записывает, какое действие нужно выполнить в 18:00. Это то, что указатель функции-члена. Это селектор поцелуя или щекотки, который был установлен ранее, создавая "состояние" для обезьяны - это тиклер или целующий, которые можно использовать позже. Более поздний код просто вызывает любую функцию, заданную без необходимости думать о возможностях или иметь какие-либо инструкции if/else-if или switch.
Чтобы вызвать его, вам нужна ссылка на экземпляр, но тогда вы можете вызвать func direct и не нуждаться в указателе на него.
Вернемся к этому. Итак, это хорошо, если вы принимаете решение о том, какое действие нужно предпринять во время компиляции (т.е. Точка X в вашей программе, это определенно будет щекотка). Указатели функций предназначены, если вы не уверены, и хотите отделить настройку действий от вызова этих действий.