Domanda

In C++, i puntatori alle funzioni membro possono essere utilizzati per puntare a membri di classi derivate (o anche di base)?

MODIFICARE:Forse un esempio aiuterà.Supponiamo di avere una gerarchia di tre classi X, Y, Z in ordine di successione.Y quindi ha una classe base X e una classe derivata Z.

Ora possiamo definire un puntatore a una funzione membro p per classe Y.Questo è scritto come:

void (Y::*p)();

(Per semplicità, suppongo che siamo interessati solo alle funzioni con la firma void f() )

Questo puntatore p ora può essere utilizzato per puntare alle funzioni membro della classe Y.

Questa domanda (due domande, in realtà) è quindi:

  1. Potere p essere utilizzato per puntare a una funzione nella classe derivata Z?
  2. Potere p essere utilizzato per puntare a una funzione nella classe base X?
È stato utile?

Soluzione

C++03 standard, §4.11 2 Puntatore alle conversioni dei membri:

Un valore di tipo "puntatore al membro di B di tipo CV T", dove B è un tipo di classe, può essere convertito in un valore di tipo "puntatore al membro di D di tipo CV T”, dove D è una classe derivata (clausola 10) di B.Se B è una classe base inaccessibile (clausola 11), ambigua (10.2) o virtuale (10.1) di D, un programma che richiede questa conversione è mal formato.Il risultato della conversione si riferisce allo stesso membro del puntatore al membro prima che avvenisse la conversione, ma si riferisce al membro della classe base come se fosse un membro della classe derivata.Il risultato si riferisce al membro nell’istanza di B di D.Poiché il risultato è di tipo “puntatore al membro di D di tipo CV T", può essere dereferenziato con un oggetto D.Il risultato è lo stesso che se il puntatore al membro di B fosse dereferenziato con il sottooggetto B di D.Il valore del puntatore del membro null viene convertito nel valore del puntatore del membro null del tipo di destinazione. 52)

52)La regola per la conversione dei puntatori a membri (da puntatore a membro di base a puntatore a membro di derivato) appare invertita rispetto alla regola per puntatori a oggetti (da puntatore a derivato a puntatore a base) (4.10, clausola 10).Questa inversione è necessaria per garantire la sicurezza del tipo.Si noti che un puntatore a un membro non è un puntatore a un oggetto o un puntatore a una funzione e le regole per le conversioni di tali puntatori non si applicano ai puntatori ai membri.In particolare, un puntatore a membro non può essere convertito in un void*.

In breve, è possibile convertire un puntatore a un membro di una classe base accessibile e non virtuale in un puntatore a un membro di una classe derivata purché il membro non sia 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
   ...

Conversione nell'altra direzione (via static_cast) è disciplinato da § 5.2.9 9:

Un valore di tipo "puntatore al membro di D di tipo cv1 T" può essere convertito in un valore di tipo "puntatore al membro di B di tipo cv2 T", dove B è una classe base (clausola 10 classe.derivato) di D, se esiste una conversione standard valida da "puntatore al membro di B di tipo T" a "puntatore al membro di D di tipo T" (4.11 conv.mem), E cv2 è la stessa qualifica cv di, o una qualifica cv maggiore di, cv1.11) Il valore del puntatore del membro null (4.11 conv.mem) viene convertito nel valore del puntatore del membro null del tipo di destinazione.Se la classe B contiene il membro originale oppure è una classe base o derivata della classe contenente il membro originale, il puntatore risultante al membro punta al membro originale.Altrimenti il ​​risultato del lancio non è definito.[Nota:sebbene non sia necessario che la classe B contenga il membro originale, il tipo dinamico dell'oggetto su cui viene dereferenziato il puntatore al membro deve contenere il membro originale;Vedere 5.5 espr.mptr.oper.]

11) I tipi di funzioni (compresi quelli utilizzati in puntatore ai tipi di funzioni del membro) non sono mai qualificati CV;Vedere 8.3.5 dcl.fct.

In breve, puoi convertire da un derivato D::* ad una base B::* se puoi convertire da a B::* ad a D::*, anche se puoi usare solo il file B::* su oggetti di tipo D o che discendono da D.

Altri suggerimenti

Non sono sicuro al 100% di quello che stai chiedendo, ma ecco un esempio che funziona con le funzioni virtuali:

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

Il problema critico con i puntatori ai membri è che possono essere applicati a qualsiasi riferimento o puntatore a una classe del tipo corretto.Ciò significa che perché Z è derivato da Y un puntatore (o riferimento) di tipo puntatore (o riferimento) a Y può effettivamente puntare (o fare riferimento) al sottooggetto della classe base di Z O qualsiasi altra classe derivato da Y.

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

Ciò significa che qualsiasi cosa assegnata a un puntatore al membro di Y deve effettivamente funzionare con qualsiasi Y.Se fosse consentito indicare un membro di Z (che non era un membro di Y) allora sarebbe possibile chiamare una funzione membro di Z su qualcosa che in realtà non era un Z.

D'altra parte, qualsiasi puntatore al membro di Y sottolinea anche il membro di Z (ereditarietà significa questo Z ha tutti gli attributi e i metodi della sua base) è legale convertire un puntatore in un membro di Y a un puntatore al membro di Z.Questo è intrinsecamente sicuro.

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

Potresti voler dare un'occhiata a questo articolo Puntatori a funzioni membro e delegati C++ più veloci possibili La risposta breve sembra essere sì, in alcuni casi.

Credo di sì.Poiché il puntatore a funzione utilizza la firma per identificarsi, il comportamento base/derivato si baserebbe su qualunque oggetto lo chiamassi.

La mia sperimentazione ha rivelato quanto segue:Attenzione: questo potrebbe essere un comportamento indefinito.Sarebbe utile se qualcuno potesse fornire un riferimento definitivo.

  1. Funzionava, ma richiedeva un cast durante l'assegnazione della funzione membro derivata a p.
  2. Anche questo ha funzionato, ma ha richiesto calchi extra durante il dereferenziamento p.

Se ci sentiamo veramente ambiziosi potremmo chiedere se p può essere utilizzato per puntare a funzioni membro di classi non correlate.Non l'ho provato, ma il FastDelegate la pagina collegata nella risposta di dagorym suggerisce che è possibile.

In conclusione, cercherò di evitare di utilizzare i puntatori alle funzioni membro in questo modo.Passaggi come i seguenti non ispirano fiducia:

Il casting tra i puntatori della funzione membro è un'area estremamente oscura.Durante la standardizzazione di C ++, ci sono state molte discussioni sul fatto che tu debba essere in grado di lanciare un puntatore della funzione membro da una classe a un puntatore della funzione membro di una base o una classe derivata e se si potesse lanciare tra classi non correlate.Quando il comitato per gli standard ha deciso, diversi fornitori di compilatore avevano già preso decisioni di attuazione che li avevano bloccati in diverse risposte a queste domande.[Articolo FastDelegate]

Supponiamo di averlo fatto class X, class Y : public X, and class Z : public Y

Dovresti essere in grado di assegnare metodi sia per X, Y a puntatori di tipo void (Y::*p)() ma non metodi per Z.Per capire perché, considera quanto segue:

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?

Consentendo tale assegnazione permettiamo l'invocazione di un metodo per Z su un oggetto Y che potrebbe portare a chissà cosa.Puoi far funzionare tutto lanciando i puntatori, ma non è sicuro o garantito che funzioni.

Ecco un esempio di ciò che funziona.È possibile sovrascrivere un metodo nella classe derivata e un altro metodo della classe base che utilizza il puntatore a questo metodo sottoposto a override chiama effettivamente il metodo della classe derivata.

#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;
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top