Зачем использовать указатели на метод-член в C ++? - PullRequest
4 голосов
/ 17 марта 2009

Многие книги и учебные пособия по С ++ объясняют, как это сделать, но я не видел ни одного, который давал бы убедительную причину сделать это.

Я очень хорошо понимаю, почему указатели на функции были необходимы в C (например, при использовании некоторых средств POSIX). Однако, AFAIK, вы не можете отправить им функцию-член из-за параметра "this". Но если вы уже используете классы и объекты, почему бы просто не использовать объектно-ориентированное решение, такое как функторы?

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

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

Ответы [ 10 ]

13 голосов
/ 17 марта 2009

Функторы не являются априори объектно-ориентированными (в C ++ термин «функтор» обычно означает структуру, определяющую operator () с произвольными аргументами и возвращаемым значением, которая может использоваться в качестве синтаксической вставки для замены реальных функций или функций указатели). Тем не менее, их объектно-ориентированная проблема имеет много проблем, в первую очередь удобство использования. Это просто много сложного шаблонного кода. Чтобы создать достойную систему сигнализации, как в большинстве диалоговых систем, возникает необходимость в большом количестве беспорядка наследования.

Здесь очень полезны указатели на функции, связанные с экземплярами (.NET демонстрирует это наглядно с делегатами).

Однако указатели на функции-члены C ++ удовлетворяют еще одну потребность. Представьте, например, что у вас есть много значений в списке, из которых вы хотите выполнить один метод, скажем, его print(). Здесь помогает указатель на функцию YourType::size, поскольку он позволяет написать такой код:

std::for_each(lst.begin(), lst.end(), std::mem_fun(&YourType::print))
3 голосов
/ 17 марта 2009

Раньше указатели на функции-члены были полезны в таких сценариях, как это:

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.

2 голосов
/ 17 марта 2009

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

Я все еще не знаю, насколько это хороший способ реализовать конечный автомат.

2 голосов
/ 17 марта 2009

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

class MyClass
{
    MyClass()
    {
        NotificationMgr::Register( FunctionPtr( this, OnNotification ) );
    }
    ~MyClass()
    {
        NotificationMgr::UnRegister( FunctionPtr( this, OnNotification ) );
    }

    void OnNotification( ... )
    {
        // handle notification
    }
};
0 голосов
/ 21 мая 2011

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

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

0 голосов
/ 18 марта 2009

Самое важное использование указателей на членов - это создание функторов. Хорошей новостью является то, что вам даже не нужно использовать его напрямую, так как он уже решен в библиотеках как 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_;
};

и создайте отдельную для каждой функции-члена, которую вы хотите вызвать. Обратите внимание, что функтор одинаков для операции и для операции 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.

0 голосов
/ 17 марта 2009

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

0 голосов
/ 17 марта 2009

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

Я не смог реализовать обратный вызов в глобальном пространстве имен, потому что он должен был обрабатывать входящие события на основе состояния объекта, который использовал сторонний API, который вызвал обратный вызов.

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

Указатель this моего объекта будет затем передан как часть Refcon для обратного вызова (который, к счастью, содержит общее назначение void*). Затем реализация пустышки использовала переданный указатель для вызова фактической и частной реализации обратного вызова, содержащегося в классе =).

Это выглядело примерно так:

public:
    void SomeClass::DummyCallback( void* pRefCon ) [ static ]
    {
        reinterpret_cast<SomeClassT*>(pRefCon)->Callback();
    }
private:
    void class SomeClass::Callback() [ static ]
    {
        // some code ...
    }
0 голосов
/ 17 марта 2009

Вы спрашивали конкретно о функциях-членах, но есть и другие способы использования указателей на функции. Наиболее распространенная причина, по которой мне нужно использовать указатели функций в C ++, - это когда я хочу загрузить DLL-библиотеку во время выполнения с помощью LoadLibrary (). Это в Windows, очевидно. В приложениях, которые используют плагины в форме необязательных библиотек DLL, динамическое связывание не может использоваться при запуске приложения, так как библиотека DLL часто не присутствует, и использование delayload является проблемой.

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

0 голосов
/ 17 марта 2009

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...