Зачем использовать указатели на метод-член в C ++?

StackOverflow https://stackoverflow.com/questions/654853

  •  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 ...
    }
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top