Pregunta

En C++, ¿se pueden utilizar punteros de función miembro para señalar a miembros de clase derivados (o incluso base)?

EDITAR:Quizás un ejemplo ayude.Supongamos que tenemos una jerarquía de tres clases. X, Y, Z por orden de herencia.Y por lo tanto tiene una clase base X y una clase derivada Z.

Ahora podemos definir un puntero de función miembro. p para clase Y.Esto se escribe como:

void (Y::*p)();

(Para simplificar, asumiré que sólo estamos interesados ​​en funciones con la firma void f() )

este puntero p ahora se puede utilizar para apuntar a funciones miembro de la clase Y.

Esta pregunta (dos preguntas, en realidad) es entonces:

  1. Poder p ser utilizado para apuntar a una función en la clase derivada Z?
  2. Poder p ser utilizado para apuntar a una función en la clase base X?
¿Fue útil?

Solución

C ++ 03 estándar, §4.11 2 Indicador de conversiones de miembros:

Un rvalue de tipo "puntero al miembro de B de tipo CV T”, donde B es un tipo de clase, se puede convertir en un valor rvalue de tipo “puntero a miembro de D de tipo CV T”, donde D es una clase derivada (cláusula 10) de B.Si B es una clase base de D inaccesible (cláusula 11), ambigua (10.2) o virtual (10.1), un programa que requiere esta conversión está mal formado.El resultado de la conversión se refiere al mismo miembro que el puntero a miembro antes de que se produjera la conversión, pero se refiere al miembro de la clase base como si fuera un miembro de la clase derivada.El resultado se refiere al miembro en la instancia de B de D.Dado que el resultado tiene tipo "puntero al miembro de D de tipo CV T”, se puede desreferenciar con un objeto D.El resultado es el mismo que si se desreferenciara el puntero al miembro de B con el subobjeto B de D.El valor del puntero de miembro nulo se convierte en el valor del puntero de miembro nulo del tipo de destino. 52)

52)La regla para la conversión de punteros a miembros (de puntero a miembro de base a puntero a miembro de derivada) aparece invertida en comparación con la regla para punteros a objetos (de puntero a derivado a puntero a base) (4.10, cláusula 10).Esta inversión es necesaria para garantizar la seguridad del tipo.Tenga en cuenta que un puntero a miembro no es un puntero a objeto ni un puntero a función y las reglas para conversiones de dichos punteros no se aplican a los punteros a miembros.En particular, un puntero a miembro no se puede convertir en un vacío*.

En resumen, puede convertir un puntero a un miembro de una clase base no virtual accesible en un puntero a un miembro de una clase derivada, siempre que el miembro no sea ambiguo.

class A {
public: 
    void foo();
};
class B : public A {};
class C {
public:
    void bar();
};
class D {
public:
    void baz();
};
class E : public A, public B, private C, public virtual D {
public: 
    typedef void (E::*member)();
};
class F:public E {
public:
    void bam();
};
...
int main() {
   E::member mbr;
   mbr = &A::foo; // invalid: ambiguous; E's A or B's A?
   mbr = &C::bar; // invalid: C is private 
   mbr = &D::baz; // invalid: D is virtual
   mbr = &F::bam; // invalid: conversion isn't defined by the standard
   ...

Conversión en la otra dirección (a través de static_cast) se rige por § 5.2.9 9:

Un rvalue de tipo "puntero al miembro de D de tipo cv1 T" se puede convertir en un rvalue de tipo "puntero a miembro de B de tipo cv2 T", donde B es una clase base (cláusula 10 clase derivada) de D, si existe una conversión estándar válida de "puntero a miembro de B de tipo T" a "puntero a miembro de D de tipo T" (4.11 conv.mem), y cv2 es la misma calificación cv que, o mayor calificación cv que, cv1.11) El valor del puntero del miembro nulo (4.11 conv.mem) se convierte en el valor de puntero de miembro nulo del tipo de destino.Si la clase B contiene el miembro original, o es una clase base o derivada de la clase que contiene el miembro original, el puntero resultante al miembro apunta al miembro original.De lo contrario, el resultado del reparto no está definido.[Nota:aunque la clase B no necesita contener el miembro original, el tipo dinámico del objeto en el que se desreferencia el puntero al miembro debe contener el miembro original;ver 5.5 operación expr.mptr.]

11) Los tipos de funciones (incluidos los utilizados en los tipos de funciones de puntero a miembro) nunca se califican con CV;ver 8.3.5 dcl.fct.

En resumen, puedes convertir desde un derivado D::* a una base B::* si puedes convertir de un B::* a un D::*, aunque sólo puedes usar el B::* sobre objetos que son del tipo D o descienden de D.

Otros consejos

No estoy 100% seguro de lo que estás preguntando, pero aquí tienes un ejemplo que funciona con funciones virtuales:

#include <iostream>
using namespace std;

class A { 
public:
    virtual void foo() { cout << "A::foo\n"; }
};
class B : public A {
public:
    virtual void foo() { cout << "B::foo\n"; }
};

int main()
{
    void (A::*bar)() = &A::foo;
    (A().*bar)();
    (B().*bar)();
    return 0;
}

El problema crítico con los punteros a miembros es que se pueden aplicar a cualquier referencia o puntero a una clase del tipo correcto.Esto significa que porque Z se deriva de Y un puntero (o referencia) de tipo puntero (o referencia) a Y en realidad puede apuntar (o referirse) al subobjeto de clase base de Z o cualquier otra clase derivado de Y.

void (Y::*p)() = &Z::z_fn; // illegal

Esto significa que cualquier cosa asignada a un puntero a miembro de Y realmente debe trabajar con cualquier Y.Si se le permitiera señalar a un miembro de Z (que no era miembro de Y) entonces sería posible llamar a una función miembro de Z en algo que en realidad no era un Z.

Por otro lado, cualquier indicador de miembro de Y también señala el miembro de Z (herencia significa que Z tiene todos los atributos y métodos de su base) ¿es legal convertir un puntero en miembro de Y a un puntero a miembro de Z.Esto es inherentemente seguro.

void (Y::*p)() = &Y::y_fn;
void (Z::*q)() = p; // legal and safe

Quizás quieras consultar este artículo. Punteros de funciones miembro y los delegados de C++ más rápidos posibles La respuesta corta parece ser sí, en algunos casos.

Eso creo.Dado que el puntero de función usa la firma para identificarse, el comportamiento base/derivado dependería del objeto al que lo haya llamado.

Mi experimentación reveló lo siguiente:Advertencia: este podría ser un comportamiento indefinido.Sería útil si alguien pudiera proporcionar una referencia definitiva.

  1. Esto funcionó, pero requirió una conversión al asignar la función miembro derivada a p.
  2. Esto también funcionó, pero requirió moldes adicionales al eliminar la referencia. p.

Si nos sentimos realmente ambiciosos, podríamos preguntarnos si p se puede utilizar para señalar funciones miembro de clases no relacionadas.No lo intenté, pero el Delegado rápido La página vinculada en la respuesta de dagorym sugiere que es posible.

En conclusión, intentaré evitar el uso de punteros a funciones miembro de esta manera.Pasajes como el siguiente no inspiran confianza:

El lanzamiento entre los punteros de la función miembro es un área extremadamente turbia.Durante la estandarización de C ++, hubo mucha discusión sobre si debería poder lanzar un puntero de la función miembro de una clase a un puntero de la función miembro de una base o clase derivada, y si podría lanzar entre clases no relacionadas.Para cuando el comité de normas se decidió, diferentes proveedores de compiladores ya habían tomado decisiones de implementación que los habían bloqueado en diferentes respuestas a estas preguntas.[Artículo de FastDelegate]

Supongamos que tenemos class X, class Y : public X, and class Z : public Y

Debería poder asignar métodos para X, Y a punteros de tipo void (Y::*p)() pero no métodos para Z.Para ver por qué considere lo siguiente:

void (Y::*p)() = &Z::func; // we pretend this is legal
Y * y = new Y; // clearly legal
(y->*p)(); // okay, follows the rules, but what would this mean?

Al permitir esa asignación, permitimos la invocación de un método para Z en un objeto Y que podría conducir a quién sabe qué.Puede hacer que todo funcione lanzando los punteros, pero eso no es seguro ni se garantiza que funcione.

A continuación se muestra un ejemplo de lo que funciona.Puede anular un método en la clase derivada, y otro método de la clase base que utiliza un puntero a este método anulado llama al método de la clase derivada.

#include <iostream>
#include <string>

using namespace std;

class A {
public:
    virtual void traverse(string arg) {
        find(&A::visit, arg);
    }

protected:
    virtual void find(void (A::*method)(string arg),  string arg) {
        (this->*method)(arg);
    }

    virtual void visit(string arg) {
        cout << "A::visit, arg:" << arg << endl;
    }
};

class B : public A {
protected:
    virtual void visit(string arg) {
        cout << "B::visit, arg:" << arg << endl;
    }
};

int main()
{
    A a;
    B b;
    a.traverse("one");
    b.traverse("two");
    return 0;
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top