Зачем использовать указатели на метод-член в C ++?
-
19-08-2019 - |
Вопрос
Многие книги и учебники по С ++ объясняют, как это сделать, но я не видел ни одного, который давал бы убедительную причину сделать это. Р>
Я очень хорошо понимаю, почему указатели на функции были необходимы в C (например, при использовании некоторых средств POSIX). Однако, AFAIK, вы не можете отправить им функцию-член из-за & Quot; this & Quot; параметр. Но если вы уже используете классы и объекты, почему бы просто не использовать объектно-ориентированное решение, такое как функторы? Р>
Примеры из реальной жизни, где вам приходилось использовать такие указатели на функции, приветствуются. Р>
Обновление: я ценю ответы каждого. Однако я должен сказать, что ни один из этих примеров на самом деле не убеждает меня в том, что это действительный механизм с точки зрения чистого ОО ...
Решение
Функторы не являются априори объектно-ориентированными (в C ++ термин & # 8220; functor & # 8221; обычно означает структуру, определяющую operator ()
с произвольными аргументами и возвращаемым значением, которое можно использовать как синтаксические вставные замены для реальных функций или указателей на функции). Тем не менее, их объектно-ориентированная проблема имеет много проблем, в первую очередь удобство использования. Это просто много сложного шаблонного кода. Для создания достойной системы сигнализации, как в большинстве диалоговых систем, возникает необходимость в большом количестве беспорядка наследования.
Здесь очень полезны указатели на функции, связанные с экземплярами (.NET демонстрирует это наглядно с делегатами).
Однако указатели на функции-члены C ++ удовлетворяют еще одну потребность. Представьте, например, что в списке есть много значений, для которых вы хотите выполнить один метод, скажем, его print()
. Здесь помогает указатель на функцию YourType::size
, поскольку он позволяет писать такой код:
std::for_each(lst.begin(), lst.end(), std::mem_fun(&YourType::print))
Другие советы
В прошлом указатели на функции-члены были полезны в таких случаях:
class Image {
// avoid duplicating the loop code
void each(void(Image::* callback)(Point)) {
for(int x = 0; x < w; x++)
for(int y = 0; y < h; y++)
callback(Point(x, y));
}
void applyGreyscale() { each(&Image::greyscalePixel); }
void greyscalePixel(Point p) {
Color c = pixels[p];
pixels[p] = Color::fromHsv(0, 0, (c.r() + c.g() + c.b()) / 3);
}
void applyInvert() { each(&Image::invertPixel); }
void invertPixel(Point p) {
Color c = pixels[p];
pixels[p] = Color::fromRgb(255 - c.r(), 255 - r.g(), 255 - r.b());
}
};
Я видел это в коммерческом приложении для рисования. (что интересно, это одна из немногих проблем C ++, лучше решаемых с помощью препроцессора).
Однако сегодня единственное использование для указателей на функции-члены находится внутри реализации boost::bind
.
Вот типичный сценарий, который мы имеем здесь. У нас есть структура уведомлений, где класс может регистрироваться для нескольких различных уведомлений. При регистрации на уведомление мы передаем указатель на функцию-член. На самом деле это очень похоже на события C #.
class MyClass
{
MyClass()
{
NotificationMgr::Register( FunctionPtr( this, OnNotification ) );
}
~MyClass()
{
NotificationMgr::UnRegister( FunctionPtr( this, OnNotification ) );
}
void OnNotification( ... )
{
// handle notification
}
};
У меня есть код, над которым я сейчас работаю, где я использовал их для реализации конечного автомата. Разыменованные функции-члены реализуют состояния, но поскольку все они находятся в классе, они получают общий объем данных, который является глобальным для всего конечного автомата. Это было бы сложно сделать с помощью обычных (не входящих в состав) указателей на функции.
Я все еще не знаю, насколько это хороший способ реализовать конечный автомат.
Это похоже на использование лямбд. Вы всегда можете передать все необходимые локальные переменные в простую функцию, но иногда вам нужно передать более одной из них. Р>
Таким образом, использование функций-членов избавит вас от передачи всех необходимых полей-членов функтору. Это все. Р>
Вы спрашивали конкретно о функциях-членах, но есть и другие способы использования указателей функций. Наиболее распространенная причина, по которой мне нужно использовать указатели функций в C ++, - это когда я хочу загрузить DLL-библиотеку во время выполнения с помощью LoadLibrary (). Это в Windows, очевидно. В приложениях, которые используют плагины в форме необязательных библиотек DLL, динамическое связывание не может использоваться при запуске приложения, поскольку библиотека DLL часто не присутствует, а использование delayload представляет собой боль.
После загрузки библиотеки вы должны получить указатель на функции, которые вы хотите использовать.
Я использовал указатели на функции-члены при разборе файла. В зависимости от конкретных строк, найденных в файле, в карте было найдено одно и то же значение и вызвана связанная функция. Это было вместо большого оператора if..else if..else, сравнивающего строки.
Самое важное использование указателей на членов - это создание функторов. Хорошая новость заключается в том, что вам даже не нужно использовать его напрямую, так как он уже решен в библиотеках как boost :: bind, но вы должны передавать указатели этим библиотекам.
class Processor
{
public:
void operation( int value );
void another_operation( int value );
};
int main()
{
Processor tc;
boost::thread thr1( boost::bind( &Processor::operation, &tc, 100 ) );
boost::thread thr2( boost::bind( &Processor::another_operation, &tc, 5 ) );
thr1.join();
thr2.join();
}
Вы можете увидеть простоту создания потока, который выполняет данную операцию над данным экземпляром класса. Р>
Простой ручной подход к вышеуказанной проблеме заключается в создании функтора самостоятельно:
class functor1
{
public:
functor1( Processor& o, int v ) : o_(o), v_(v) {}
void operator()() {
o_.operation( v_ ); // [1]
}
private:
Processor& o_;
int v_;
};
и создайте отдельную для каждой функции-члена, которую вы хотите вызвать. Обратите внимание, что функтор абсолютно одинаков для operation и another_operation , но вызов в [1] должен быть повторен в обоих функторах. Используя указатель на функцию-член, вы можете написать простой функтор:
class functor
{
public:
functor( void (*Processor::member)(int), Processor& p, int value )
: member_( member ), processor_(p), value_( value ) {}
void operator()() {
p.*member(value_);
}
private:
void (*Processor::member_)(int);
Processor& processor_;
int value;
};
и используйте его
int main() {
Processor p;
boost::thread thr1( functor( &Processor::operation, p, 100 ) );
boost::thread thr2( functor( &Processor::another_operation, p, 5 ) );
thr1.join();
thr2.join();
}
Опять же, вам даже не нужно определять этот функтор, поскольку boost :: bind сделает это за вас. Будущий стандарт будет иметь свою собственную версию bind в соответствии с реализацией Boost.
Указатель на функцию-член не зависит от объекта. Вам это нужно, если вы хотите ссылаться на функцию по значению во время выполнения (или как параметр шаблона). Это вступает в свои права, когда вы не имеете в виду ни одного объекта, чтобы назвать его. Р>
Итак, если вы знаете функцию, но не знаете объект И хотите передать эти знания по значению, то функция «точка-член» является единственным предписанным решением. Пример Ираимбиланья хорошо это иллюстрирует. Это может помочь вам увидеть мой пример использования переменной-члена . Принцип тот же. Р>
Я использовал указатель на функцию-член в сценарии, где мне нужно было предоставить указатель на функцию обратного вызова с предопределенным списком параметров (чтобы я не мог передать произвольные параметры) некоторому стороннему объекту API. Р>
Я не смог реализовать обратный вызов в глобальном пространстве имен, потому что он должен был обрабатывать входящие события на основе состояния объекта, который использовал сторонний API, который вызвал обратный вызов. Р>
Поэтому я хотел, чтобы реализация обратного вызова была частью класса, в котором использовался сторонний объект. Я объявил открытую и статическую функцию-член в классе, в котором я хотел реализовать обратный вызов, и передал указатель на него на объект API (ключевое слово static
избавило меня от проблемы с указателем this
). р>
Указатель void*
моего объекта будет передан как часть Refcon для обратного вызова (который, к счастью, содержит общее назначение <=>).
Затем реализация пустышки использовала переданный указатель для вызова фактической и частной реализации обратного вызова, содержащегося в классе =).
Это выглядело примерно так:
public:
void SomeClass::DummyCallback( void* pRefCon ) [ static ]
{
reinterpret_cast<SomeClassT*>(pRefCon)->Callback();
}
private:
void class SomeClass::Callback() [ static ]
{
// some code ...
}