resueltos programas programar herencia funciones ejercicios ejemplos como comandos codigo clases c++ inheritance oop member-function-pointers

programas - funciones en c++



Indicadores de herencia de C++ y función de miembro (8)

Aquí hay 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 el puntero a este método reemplazado realmente 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; }

En C ++, ¿se pueden usar punteros de función de miembro para señalar miembros de clase derivados (o incluso de 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)();

(Por simplicidad, asumiré que solo estamos interesados ​​en 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:

  1. ¿Puede p usarse para señalar una función en la clase derivada Z ?
  2. ¿Puede p usarse para señalar una función en la clase base X ?

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

Un valor de tipo "puntero a miembro de B de tipo cv T", donde B es un tipo de clase, se puede convertir a un valor 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, un programa que necesita esta conversión está mal formado. El resultado de la conversión se refiere al mismo miembro que el puntero al miembro antes de que tuviera lugar 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 D de B. Dado que el resultado tiene el tipo "puntero a miembro de D de tipo cv T", puede desreferenciarse con un objeto D. El resultado es el mismo que si el apuntador al miembro de B fuera desreferenciado con el subobjeto B de D. El valor de puntero de miembro nulo se convierte al valor de 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 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 al miembro no es un puntero al objeto o un puntero a la función y las reglas para las conversiones de dichos punteros no se aplican a los punteros a los miembros. En particular, un puntero a miembro no se puede convertir a un vacío *.

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

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

Un valor de r "tipo de puntero a miembro de D de tipo cv1 T" se puede convertir a un valor de tipo "puntero a miembro de B de tipo cv2 T", donde B es una clase base (cláusula 10 class.derived ) de D , si existe una conversión estándar válida de "apuntador a miembro de B de tipo T" a "apuntador a miembro de D de tipo T" ( 4.11 conv.mem ), y cv2 es la misma calificación cv, o mayor cv- calificación que, cv1 . 11) El valor del puntero del miembro nulo ( 4.11 conv.mem ) se convierte al valor del puntero del 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 lanzamiento no está definido. [Nota: aunque la clase B no necesita contener el miembro original, el tipo dinámico del objeto sobre el que se quita la referencia al miembro debe contener el miembro original; ver 5.5 expr.mptr.oper .]

11) Los tipos de funciones (incluidos los utilizados en el puntero a los tipos de funciones miembro) nunca son cv-qualified; ver 8.3.5 dcl.fct .

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


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) de tipo puntero (o referencia) a Y puede apuntar (o referirse) al subobjeto de clase base de Z o a cualquier otra clase derivada de Y

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

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

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

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



Eso creo. Como el puntero de función usa la firma para identificarse, el comportamiento base / derivado se basaría en cualquier objeto sobre el que lo invocó.


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ó un lanzamiento cuando se asignó la función miembro derivada a p .
  2. Esto también funcionó, pero requirió cambios adicionales al desreferenciar p .

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

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

Casting entre punteros de función miembro es un área extremadamente turbia. Durante la estandarización de C ++, hubo mucha discusión sobre si debería poder convertir un puntero de función miembro de una clase a un puntero de función miembro de una clase base o derivada, y si se podía convertir entre clases no relacionadas. Para cuando el comité de estándares tomó una decisión, 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 ]


No estoy 100% seguro de lo que estás preguntando, 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; }


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 pulsando los punteros, pero eso no es seguro ni está garantizado que funcione.