Question

En C++, les pointeurs de fonctions membres peuvent-ils être utilisés pour pointer vers des membres de classe dérivés (ou même de base) ?

MODIFIER:Peut-être qu'un exemple aidera.Supposons que nous ayons une hiérarchie de trois classes X, Y, Z par ordre d'héritage.Y a donc une classe de base X et une classe dérivée Z.

Nous pouvons maintenant définir un pointeur de fonction membre p pour la classe Y.Ceci s'écrit ainsi :

void (Y::*p)();

(Pour plus de simplicité, je suppose que nous ne nous intéressons qu'aux fonctions avec la signature void f() )

Ce pointeur p peut maintenant être utilisé pour pointer vers les fonctions membres de la classe Y.

Cette question (deux questions, en fait) est alors :

  1. Peut p être utilisé pour pointer vers une fonction dans la classe dérivée Z?
  2. Peut p être utilisé pour pointer vers une fonction dans la classe de base X?
Était-ce utile?

La solution

C++03 standard, §4.11 2 Pointeur vers les conversions de membres:

Une rvalue de type « pointeur vers un membre de B de type CV T », où B est un type de classe, peut être converti en une rvalue de type « pointeur vers un membre de D de type CV T », où D est une classe dérivée (article 10) de B.Si B est une classe de base inaccessible (clause 11), ambiguë (10.2) ou virtuelle (10.1) de D, un programme qui nécessite cette conversion est mal formé.Le résultat de la conversion fait référence au même membre que le pointeur vers le membre avant la conversion, mais il fait référence au membre de la classe de base comme s'il était membre de la classe dérivée.Le résultat fait référence au membre dans l’instance D de B.Puisque le résultat est de type « pointeur vers un membre de D de type CV T", il peut être déréférencé avec un objet D.Le résultat est le même que si le pointeur vers le membre de B était déréférencé avec le sous-objet B de D.La valeur du pointeur de membre nul est convertie en valeur du pointeur de membre nul du type de destination. 52)

52)La règle de conversion des pointeurs vers les membres (du pointeur vers le membre de la base vers le pointeur vers le membre du dérivé) apparaît inversée par rapport à la règle pour les pointeurs vers les objets (du pointeur vers le dérivé vers le pointeur vers la base) (4.10, clause 10).Cette inversion est nécessaire pour assurer la sécurité du type.Notez qu'un pointeur vers un membre n'est pas un pointeur vers un objet ou un pointeur vers une fonction et que les règles de conversion de ces pointeurs ne s'appliquent pas aux pointeurs vers des membres.En particulier, un pointeur vers un membre ne peut pas être converti en void*.

En bref, vous pouvez convertir un pointeur vers un membre d'une classe de base accessible et non virtuelle en un pointeur vers un membre d'une classe dérivée tant que le membre n'est pas ambigu.

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
   ...

Conversion dans l'autre sens (via static_cast) est régi par § 5.2.9 9:

Une rvalue de type "pointeur vers un membre de D de type CV1 T" peut être converti en une rvalue de type "pointeur vers un membre de B de type CV2 T", où B est une classe de base (clause 10 classe.dérivé) de D, s'il existe une conversion standard valide de "pointeur vers un membre de B de type T" en "pointeur vers un membre de D de type T" (4.11 conv.mem), et CV2 est le même CV que, ou un CV supérieur à, CV1.11) La valeur du pointeur de membre nul (4.11 conv.mem) est converti en valeur de pointeur de membre null du type de destination.Si la classe B contient le membre d’origine ou est une classe de base ou dérivée de la classe contenant le membre d’origine, le pointeur résultant vers le membre pointe vers le membre d’origine.Sinon, le résultat du casting n'est pas défini.[Note:bien que la classe B ne contienne pas nécessairement le membre d'origine, le type dynamique de l'objet sur lequel le pointeur vers le membre est déréférencé doit contenir le membre d'origine ;voir 5.5 expr.mptr.oper.]

11) Les types de fonction (y compris ceux utilisés dans les types de fonctions de pointeur vers les membres) ne sont jamais qualifiés CV;voir 8.3.5 dcl.fct.

En bref, vous pouvez convertir à partir d'un dérivé D::* à une base B::* si vous pouvez convertir à partir d'un B::* à un D::*, bien que vous ne puissiez utiliser que le B::* sur les objets qui sont de type D ou qui descendent de D.

Autres conseils

Je ne suis pas sûr à 100 % de ce que vous demandez, mais voici un exemple qui fonctionne avec des fonctions virtuelles :

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

Le problème critique avec les pointeurs vers des membres est qu’ils peuvent être appliqués à n’importe quelle référence ou pointeur vers une classe du type correct.Cela signifie que parce que Z dérive de Y un pointeur (ou référence) de type pointeur (ou référence) vers Y peut en fait pointer (ou faire référence) au sous-objet de classe de base de Z ou toute autre classe dérivé de Y.

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

Cela signifie que tout ce qui est affecté à un pointeur vers un membre de Y doit réellement fonctionner avec n'importe quel Y.S'il était permis de désigner un membre de Z (ce n'était pas membre de Y) alors il serait possible d'appeler une fonction membre de Z sur quelque chose qui n'était pas vraiment un Z.

En revanche, tout pointeur vers un membre de Y souligne également le membre de Z (l'héritage signifie que Z possède tous les attributs et méthodes de sa base) est-il légal de convertir un pointeur en membre de Y vers un pointeur vers un membre de Z.C’est intrinsèquement sûr.

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

Vous voudrez peut-être consulter cet article Pointeurs de fonctions membres et délégués C++ les plus rapides possibles La réponse courte semble être oui, dans certains cas.

Je le crois.Étant donné que le pointeur de fonction utilise la signature pour s'identifier, le comportement de base/dérivé dépendrait de l'objet sur lequel vous l'avez appelé.

Mon expérimentation a révélé ce qui suit :Attention : il peut s'agir d'un comportement indéfini.Il serait utile que quelqu'un puisse fournir une référence définitive.

  1. Cela a fonctionné, mais a nécessité un cast lors de l'attribution de la fonction membre dérivée à p.
  2. Cela a également fonctionné, mais a nécessité des conversions supplémentaires lors du déréférencement. p.

Si nous nous sentons vraiment ambitieux, nous pourrions nous demander si p peut être utilisé pour pointer vers des fonctions membres de classes non liées.Je ne l'ai pas essayé, mais le Délégué rapide la page liée dans la réponse de dagorym suggère que c'est possible.

En conclusion, j'essaierai d'éviter d'utiliser les pointeurs de fonctions membres de cette manière.Des passages comme ceux-ci n’inspirent pas confiance :

Le coulage entre les pointeurs de la fonction des membres est une zone extrêmement trouble.Au cours de la normalisation de C ++, il y a eu beaucoup de discussions sur la question de savoir si vous devriez pouvoir lancer un pointeur de fonction membre d'une classe à un pointeur de fonction membre d'une base ou d'une classe dérivée, et si vous pouviez lancer entre des classes non liées.Au moment où le comité des normes se décidait, différents fournisseurs de compilateurs avaient déjà pris des décisions de mise en œuvre qui les avaient enfermés dans différentes réponses à ces questions.[Article de FastDelegate]

Supposons que nous ayons class X, class Y : public X, and class Z : public Y

Vous devriez pouvoir attribuer des méthodes pour X et Y à des pointeurs de type void (Y::*p)() mais pas aux méthodes pour Z.Pour comprendre pourquoi, considérez ce qui suit :

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?

En autorisant cette affectation, nous autorisons l'invocation d'une méthode pour Z sur un objet Y qui pourrait conduire à on ne sait quoi.Vous pouvez faire en sorte que tout fonctionne en lançant les pointeurs, mais ce n'est pas sûr ni garanti de fonctionner.

Voici un exemple de ce qui fonctionne.Vous pouvez remplacer une méthode dans une classe dérivée, et une autre méthode de la classe de base qui utilise le pointeur vers cette méthode remplacée appelle en effet la méthode de la classe dérivée.

#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;
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top