Herencia de C++ y punteros de función miembro


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

EDITAR: Tal vez un ejemplo ayude. Supongamos que tenemos una jerarquía de tres clases X, Y, Z en 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 la clase Y. Esto está escrito como:

void (Y::*p)();

(Para simplificar, asumiré que solo nos interesan las funciones con la firma void f() )

Este puntero p ahora se puede usar para apuntar a funciones miembro de la clase Y.

Esta pregunta (dos preguntas, realmente) es entonces: {[17]]}

  1. ¿Se puede usar p para apuntar a una función en la clase derivada Z?
  2. ¿Se puede usar p para apuntar a una función en la clase base X?
Author: smh, 2008-09-13

8 answers

C++03 std, §4.11 2 Puntero a conversiones de miembros :

Un rvalue de tipo "puntero a miembro de B de tipo cv T," donde B es un tipo de clase, se puede convertir en un 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 inaccesible (cláusula 11), ambigua (10.2) o virtual (10.1) de D, esta conversión está mal formada. El resultado de la conversión se refiere al mismo miembro que el puntero a miembro antes de que la conversión tuviera lugar, 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 D de B. Dado que el resultado tiene el tipo "puntero al miembro de D de tipo cv T," puede ser desreferenciado con un objeto D. El resultado es el mismo que si el puntero al miembro de B se desreferenciara con el subobjeto B de D. El valor del puntero del miembro nulo se convierte en el nulo valor del puntero de miembro 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 derivado) 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 o un puntero a función y las reglas para las conversiones de tal punteros no se aplican a los punteros a miembros. En particular, un puntero a miembro no se puede convertir en un void*.

En resumen, puede convertir un puntero a un miembro de una clase base accesible, no virtual, en un puntero a un miembro de una clase derivada, siempre y cuando 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
   ...

La conversión en la otra dirección (vía static_cast) se rige por § 5.2.9 9:

Un rvalue de tipo " puntero a miembro de D de tipo cv1 T " se puede convertir a un rvalue de tipo "puntero a miembro de B de tipo cv2 T", donde B es una clase base (clause 10 class.derived) of D, if a valid standard conversion from "pointer to member of B of type T" to "pointer to member of D of type T" exists (4.11 conv.mem ), y cv2 es la misma cualificación cv que, o mayor que, cv1.11) El valor del puntero de miembro nulo ( 4.11 conv.mem ) se convierte a la 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 es indefinido. [Nota: aunque la clase B no necesita contener el miembro original, el tipo dinámico del objeto en el que se desreferencie el puntero al miembro debe contener el miembro original; véase 5.5 expr.mptr.oper .]

11) Tipos de función (incluidos los utilizados en la función puntero a miembro tipos) nunca están calificados para cv; ver 8.3.5 dcl.fct .

En resumen, puede convertir de un D::* derivado a una base B::* si puede convertir de un B::* a un D::*, aunque solo puede usar el B::* en objetos que son de tipo D o descienden de D.

 28
Author: outis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-07 08:00:42

No estoy 100% seguro de lo que estás pidiendo, pero aquí hay 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;
}
 10
Author: Matt Price,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-12 21:42:07

El problema crítico con los punteros a los miembros es que se pueden aplicar a cualquier referencia o puntero a una clase del tipo correcto. Esto significa que debido a que Z se deriva de Y, un puntero (o referencia) del tipo puntero (o referencia) a Y puede apuntar (o referirse) a la clase base sub-objeto de Z o cualquier otra clase derivada de Y.

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

Esto significa que cualquier cosa asignada a un puntero a miembro de Y debe funcionar realmente con cualquier Y. Si se permite 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 sobre algo que no era realmente un Z.

Por otro lado, cualquier puntero a miembro de Y también apunta al miembro de Z (la herencia significa que Z tiene todos los atributos y métodos de su base) es legal convertir un puntero a 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
 6
Author: CB Bailey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-04-22 06:58:06

Es posible que desee consultar este artículo Los Punteros de Funciones Miembro y los Delegados de C++ más Rápidos Posibles La respuesta corta parece ser sí, en algunos casos.

 3
Author: dagorym,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-12 21:35:05

Creo que sí. Dado que el puntero de la función usa la firma para identificarse, el comportamiento base/derivado dependería de cualquier objeto en el que lo llamaras.

 1
Author: Steve Duitsman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-12 21:30:00

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

  1. Esto funcionó, pero requirió un cast al asignar la función miembro derivada a p.
  2. Esto también funcionó, pero requirió lanzamientos adicionales al desreferenciar p.

Si nos sentimos realmente ambiciosos, podríamos preguntar si p se puede usar para apuntar a funciones miembro de clases no relacionadas. No lo intenté., pero la página FastDelegate enlazada en la respuesta de dagorym sugiere que es posible.

En conclusión, intentaré evitar usar punteros de función miembro de esta manera. Pasajes como los siguientes no inspiran confianza:

Fundición entre función miembro pointers es un área extremadamente turbia. Durante la estandarización de C++, hubo mucha discusión sobre si usted debe ser capaz de lanzar un puntero de función miembro de una clase a un miembro puntero de función de una base o clase derivada, y si usted podría lanzarse entre clases no relacionadas. En el momento en que el comité de normas tomaron una decisión, compilador diferente los proveedores ya habían hecho decisiones de ejecución encerrados en diferentes respuestas a estas preguntas. [Artículo de delegación rápida ]

 1
Author: smh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-07-15 10:53:14

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

Debería ser capaz de asignar métodos para ambos X, Y a punteros de tipo void (Y::*p)() pero no métodos para Z. Para ver por qué considerar 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 llevar a quién sabe qué. Puede hacer que todo funcione lanzando los punteros, pero eso no es seguro ni está garantizado para funcionar.

 1
Author: Winston Ewert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-11-28 15:04:10

Aquí hay un ejemplo de lo que funciona. Puede sobrescribir un método en clase derivada, y otro método de clase base que usa puntero a este método invalidado llama de hecho 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;
}
 0
Author: Gena Batsyan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-13 18:58:03