Pregunta

Muchos libros y tutoriales de C ++ explican cómo hacer esto, pero no he visto uno que ofrezca una razón convincente para elegir hacer esto.

Entiendo muy bien por qué los punteros de función eran necesarios en C (por ejemplo, cuando se utilizan algunas instalaciones POSIX). Sin embargo, AFAIK no puede enviarles una función miembro debido a " this " parámetro. Pero si ya está usando clases y objetos, ¿por qué no usar una solución orientada a objetos como los functors?

Se agradecerían los ejemplos del mundo real de dónde tenía que usar dichos punteros de función.

Actualización: agradezco las respuestas de todos. Sin embargo, debo decir que ninguno de estos ejemplos realmente me convence de que este es un mecanismo válido desde una perspectiva puramente pura ...

¿Fue útil?

Solución

Los functores no están orientados a objetos a priori (en C ++, el término & # 8220; functor & # 8221; generalmente significa una estructura que define un operador () con argumentos arbitrarios y un valor de retorno que puede ser se utiliza como reemplazos sintácticos directos a funciones reales o punteros de función). Sin embargo, su problema orientado a objetos tiene muchos problemas, principalmente la usabilidad. Es solo un montón de código complicado repetitivo. Para un marco de señalización decente como en la mayoría de los marcos de diálogo, se hace necesario un montón de problemas de herencia.

Los punteros de función vinculados a la instancia serían muy beneficiosos aquí (.NET lo demuestra ampliamente con los delegados).

Sin embargo, los punteros de función miembro de C ++ satisfacen otra necesidad aún. Imagine, por ejemplo, que tiene muchos valores en una lista de los cuales desea ejecutar un método, digamos su print () . Un puntero de función a YourType :: size ayuda aquí porque le permite escribir dicho código:

std::for_each(lst.begin(), lst.end(), std::mem_fun(&YourType::print))

Otros consejos

En el pasado, los punteros de funciones miembro solían ser útiles en escenarios como este:

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());
    }
};

He visto eso usado en una aplicación de pintura comercial. (Curiosamente, es uno de los pocos problemas de C ++ mejor resueltos con el preprocesador).

Hoy, sin embargo, el único uso para los punteros de funciones miembro es dentro de la implementación de boost :: bind .

Aquí hay un escenario típico que tenemos aquí. Tenemos un marco de notificaciones, donde una clase puede registrarse en múltiples notificaciones diferentes. Al registrarse en una notificación, pasamos el puntero de la función miembro. Esto es realmente muy similar a los eventos de C #.

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

    void OnNotification( ... )
    {
        // handle notification
    }
};

Tengo un código en el que estoy trabajando ahora mismo donde los usé para implementar una máquina de estado. Las funciones miembro desreferenciadas implementan los estados, pero dado que todos están en la clase, pueden compartir una cantidad certian de datos que es global para toda la máquina de estados. Eso habría sido difícil de lograr con punteros de función normales (no miembros).

Todavía estoy indeciso sobre si esta es una buena manera de implementar una máquina de estado.

Es como usar lambdas. Siempre puede pasar todas las variables locales necesarias a una función simple, pero a veces tiene que pasar más de una.

Por lo tanto, el uso de funciones miembro le ahorrará pasar todos los campos miembros necesarios a un functor. Eso es todo.

Usted preguntó específicamente sobre las funciones miembro, pero también hay otros usos para los punteros de función. La razón más común por la que necesito usar punteros de función en C ++ es cuando quiero cargar un tiempo de ejecución de DLL usando LoadLibrary (). Esto está en Windows, obviamente. En las aplicaciones que usan complementos en forma de archivos DLL opcionales, la vinculación dinámica no se puede usar al inicio de la aplicación, ya que el archivo DLL a menudo no estará presente, y el uso de delayload es una molestia.

Después de cargar la biblioteca, debe obtener un puntero a las funciones que desea utilizar.

He usado punteros de funciones miembro para analizar un archivo. Dependiendo de las cadenas específicas encontradas en el archivo, se encontró el mismo valor en un mapa y se llamó a la función asociada. Esto era en lugar de una gran declaración if..else if..else que compara cadenas.

El uso más importante de los punteros de miembros es la creación de functores. La buena noticia es que apenas necesita usarlo directamente, ya que ya está resuelto en bibliotecas como boost :: bind, pero debe pasar los punteros a esas bibliotecas.

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();
}

Puede ver la simplicidad de crear un hilo que ejecute una operación dada en una instancia dada de una clase.

El enfoque simple y artesanal del problema anterior estaría en la línea de crear un functor usted mismo:

class functor1
{
public:
    functor1( Processor& o, int v ) : o_(o), v_(v) {}
    void operator()() {
        o_.operation( v_ ); // [1]
    }
private:
    Processor& o_;
    int v_;
};

y cree una diferente para cada función miembro que desee llamar. Tenga en cuenta que el functor es exactamente el mismo para operation y para another_operation , pero la llamada en [1] debería replicarse en ambos functors. Usando un puntero de función miembro puede escribir un functor simple:

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;
};

y úsalo:

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();
}

Por otra parte, ni siquiera necesita definir ese functor ya que boost :: bind lo hace por usted. El próximo estándar tendrá su propia versión de bind a lo largo de las líneas de implementación de boost.

Un puntero a una función miembro es independiente del objeto. Lo necesita si desea hacer referencia a una función por valor en tiempo de ejecución (o como parámetro de plantilla). Viene por sí mismo cuando no tienes un solo objeto en mente sobre el cual llamarlo.

Entonces, si conoce la función, pero no conoce el objeto Y desea transmitir este conocimiento por valor, entonces la función punto a miembro es la única solución prescrita . El ejemplo de Iraimbilanja ilustra esto bien. Puede ayudarlo a ver mi ejemplo de uso de una variable miembro . El principio es el mismo.

Utilicé un puntero de función a una función miembro en un escenario en el que tuve que proporcionar un puntero de función a una devolución de llamada con una lista de parámetros predefinida (por lo que no pude pasar parámetros arbitrarios) a algún objeto API de terceros.

No pude implementar la devolución de llamada en el espacio de nombres global porque se suponía que debía manejar los eventos entrantes en función del estado del objeto que utilizaba la API de terceros que había desencadenado la devolución de llamada.

Así que quería que la implementación de la devolución de llamada fuera parte de la clase que hizo uso del objeto de terceros. Lo que hice fue declarar una función miembro pública y estática en la clase en la que quería implementar la devolución de llamada y pasarle un puntero al objeto API (la palabra clave static evitando el este problema con el puntero).

El puntero this de mi objeto se pasaría como parte de Refcon para la devolución de llamada (que afortunadamente contenía un void * de propósito general). La implementación del dummy luego usó el puntero pasado para invocar la implementación real y privada de la devolución de llamada contenida en la clase =).

Se parecía a esto:

public:
    void SomeClass::DummyCallback( void* pRefCon ) [ static ]
    {
        reinterpret_cast<SomeClassT*>(pRefCon)->Callback();
    }
private:
    void class SomeClass::Callback() [ static ]
    {
        // some code ...
    }
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top