Domanda

Potrei sbagliarmi qui, ma a quanto ho capito, C ++ non ha davvero un " nativo; puntatore alla funzione membro " genere. So che puoi fare acrobazie con Boost e mem_fun ecc. Ma perché i progettisti di C ++ hanno deciso di non avere un puntatore a 64 bit contenente un puntatore alla funzione e un puntatore all'oggetto, ad esempio?

Ciò che intendo specificamente è un puntatore a una funzione membro di un particolare di tipo sconosciuto . OSSIA qualcosa che puoi usare per un callback . Questo sarebbe un tipo che contiene due valori. Il primo valore è un puntatore alla funzione e il secondo valore è un puntatore alla istanza specifica dell'oggetto.

Ciò che non intendo è un puntatore a una funzione membro generale di una classe. Per es.

int (Fred::*)(char,float)

Sarebbe stato così utile e mi avrebbe semplificato la vita.

Hugo

È stato utile?

Soluzione

@RocketMagnet - Questo è in risposta al tuo altra domanda , quella che è stata etichettata come duplicata. Sto rispondendo a quella , non questa.

In generale, il puntatore C ++ alle funzioni membro non può essere trasferito in modo portabile nella gerarchia di classi. Detto questo, spesso puoi cavartela. Ad esempio:

#include <iostream>
using std::cout;
class A { public: int x; };
class B { public: int y; };
class C : public B, public A { public: void foo(){ cout << "a.x == " << x << "\n";}};

int main() {
    typedef void (A::*pmf_t)();
    C c; c.x = 42; c.y = -1;

    pmf_t mf = static_cast<pmf_t>(&C::foo);
    (c.*mf)();
}

Compila questo codice e il compilatore giustamente si lamenta:

$ cl /EHsc /Zi /nologo pmf.cpp
pmf.cpp
pmf.cpp(15) : warning C4407: cast between different pointer to member representations, compiler may generate incorrect code

$

Quindi, per rispondere " perché C ++ non ha una funzione pointer-to-member-on-void-class? " è che questa immaginaria classe-base-di-tutto non ha membri, quindi non c'è valore che tu possa assegnargli in sicurezza! " void (C :: ) () " e " void (void :: ) () " sono tipi reciprocamente incompatibili.

Ora, scommetto che stai pensando " aspetta, ho lanciato i puntatori-funzione-membro proprio prima! " Sì, potresti avere, usando reinterpret_cast e la singola eredità. È nella stessa categoria di altri cast reinterpretati:

#include <iostream>
using std::cout;
class A { public: int x; };
class B { public: int y; };
class C : public B, public A { public: void foo(){ cout << "a.x == " << x << "\n";}};
class D { public: int z; };

int main() {
    C c; c.x = 42; c.y = -1;

    // this will print -1
    D& d = reinterpret_cast<D&>(c);
    cout << "d.z == " << d.z << "\n";
}

Quindi se void (void::*)() esistesse, ma non c'è nulla che tu possa assegnargli in modo sicuro / portabile.

Tradizionalmente, usi le funzioni della firma void (*)(void*) ovunque ti piacerebbe usare <=>, perché mentre i puntatori della funzione membro non eseguono il cast su e giù per l'ereditarietà dell'ereditarietà, i puntatori vuoti lo fanno bene. Invece:

#include <iostream>
using std::cout;
class A { public: int x; };
class B { public: int y; };
class C : public B, public A { public: void foo(){ cout << "a.x == " << x << "\n";}};

void do_foo(void* ptrToC){
    C* c = static_cast<C*>(ptrToC);
    c->foo();
}

int main() {
    typedef void (*pf_t)(void*);
    C c; c.x = 42; c.y = -1;

    pf_t f = do_foo;
    f(&c);
}

Quindi alla tua domanda. Perché il C ++ non supporta questo tipo di casting. I tipi di funzioni da puntatore a membro devono già avere a che fare con classi di base virtuali vs non virtuali e funzioni di membro virtuali contro non virtuali, tutte dello stesso tipo, gonfiandole a 4 * sizeof (void *) su alcune piattaforme. Penso che complicherebbe ulteriormente l'implementazione della funzione da puntatore a membro e che i puntatori di funzioni non elaborate risolvono già così bene questo problema.

Come altri hanno commentato, C ++ offre agli autori delle biblioteche strumenti sufficienti per farlo, e quindi i programmatori "normali" come te e me dovrebbero usare quelle librerie invece di sudare questi dettagli.

EDIT: contrassegnato wiki della comunità. Modifica solo per includere riferimenti pertinenti allo standard C ++ e aggiungi in corsivo. (esp. aggiungi riferimenti a standard in cui la mia comprensione era sbagliata! ^ _ ^)

Altri suggerimenti

Come altri hanno sottolineato, C ++ ha un tipo di puntatore a funzione membro.

Il termine che stavi cercando è " funzione associata " ;. Il motivo per cui C ++ non fornisce zucchero di sintassi per l'associazione di funzioni è dovuto alla sua filosofia di fornire solo gli strumenti più elementari, con i quali è quindi possibile creare tutto ciò che si desidera. Questo aiuta a mantenere la lingua & Quot; piccola & Quot; (o almeno, meno mente incredibilmente enorme).

Allo stesso modo, C ++ non ha una primitiva lock {} come quella di C # ma ha RAII che viene usato da scoped_lock di boost.

C'è ovviamente la scuola di pensiero che dice che dovresti aggiungere zucchero di sintassi per tutto ciò che potrebbe essere utile. Nel bene e nel male, il C ++ non appartiene a quella scuola.

Penso che ciò che stai cercando potrebbe essere in queste librairie ...

Delegati veloci http://www.codeproject.com/KB/cpp/FastDelegate aspx

Boost.Function http: //www.boost. org / doc / librerie / 1_37_0 / doc / html / function.html

Ed ecco una spiegazione molto completa delle domande relative al puntatore a funzione http://www.parashift.com/c++-faq-lite/pointers-to-members.html

Lo fa.

Ad esempio,

int (Fred::*)(char,float)

è un puntatore a una funzione membro di una classe Fred che restituisce un int e accetta un char e un float.

Penso che la risposta sia che i progettisti di C ++ scelgono di non avere nel linguaggio le cose che potrebbero essere implementate altrettanto facilmente in una libreria. La tua descrizione di ciò che desideri offre un modo perfettamente ragionevole per implementarlo.

So che sembra divertente, ma C ++ è un linguaggio minimalista. Hanno lasciato alle biblioteche tutto ciò che potevano lasciare loro.

Il TR1 ha la funzione std :: tr1 :: e verrà aggiunto a C ++ 0x. Quindi in un certo senso ce l'ha.

Una delle filosofie progettuali del C ++ è: non paghi per ciò che non usi. Il problema con i deliri in stile C # è che sono pesanti e richiedono il supporto linguistico, che tutti pagherebbero per usarli o meno. Ecco perché è preferita l'implementazione della libreria.

Il motivo per cui i delegati sono pesanti è che un puntatore a metodo è spesso più grande di un puntatore normale. Questo accade ogni volta che il metodo è virtuale. Il puntatore al metodo chiamerà una funzione diversa a seconda della classe base che lo utilizza. Ciò richiede almeno due puntatori, la vtable e l'offset. C'è un'altra stranezza coinvolta nel metodo da una classe coinvolta in eredità multipla.

Detto questo, non sono uno scrittore di compilatori. Potrebbe essere stato possibile creare un nuovo tipo per i puntatori del metodo associato che sovvertirebbe la virtualità del metodo a cui si fa riferimento (dopo tutto sappiamo quale sia la classe base se il metodo è associato).

Il problema non è sicuramente la base di avere un puntatore a oggetti e un puntatore a funzione in un pacchetto facile da usare, perché potresti farlo usando un puntatore e un thunk. (Tali thunk sono già utilizzati da VC ++ su x86 per supportare i puntatori alle funzioni dei membri virtuali, in modo che questi puntatori occupino solo 4 byte.) Potresti finire con molti thunk, è vero, ma le persone si affidano già al linker per elimina le duplicazioni di modelli duplicati - lo so, comunque - e ci sono solo così tante vtable e questo offset con cui finirai in pratica. Il sovraccarico probabilmente non sarebbe significativo per nessun programma di dimensioni ragionevoli, e se non usi questa roba non ti costerà nulla.

(Le architetture che usano tradizionalmente un sommario memorizzano il puntatore sommario nella parte del puntatore a funzione, anziché in thunk, proprio come dovrebbero già fare.)

(Questo nuovo tipo di oggetto non sarebbe esattamente sostituibile affinché un normale puntatore funzioni, ovviamente, perché le dimensioni sarebbero diverse. Tuttavia, verrebbero scritte uguali nel punto di chiamata.)

Il problema che vedo è quello della convenzione di chiamata: supportare i puntatori a funzioni in questo modo potrebbe essere complicato nel caso generale, perché il codice generato dovrebbe preparare gli argomenti (incluso) allo stesso modo, indipendentemente dal tipo effettivo di cosa, funzione o funzione membro, a cui punta il puntatore.

Questo probabilmente non è un grosso problema su x86, almeno non con questa chiamata, perché potresti semplicemente caricare ECX a prescindere e accettare che se la funzione chiamante non ne ha bisogno, sarà falsa. (E penso che VC ++ supponga che ECX sia fasullo in questo caso comunque.) Ma su architetture che passano argomenti a parametri nominati nei registri a funzioni, si può finire con una discreta quantità di shuffle nel thunk e se gli argomenti dello stack vengono spinti a sinistra a destra allora sei praticamente pieno. E questo non può essere risolto staticamente, perché nel limite non ci sono informazioni sull'unità di traduzione incrociata.

[Modifica: MSalters, in un commento al post di rocketmagnet sopra, sottolinea che se si conoscono sia l'oggetto E la funzione, questo offset e così via possono essere determinati immediatamente. Questo non mi è venuto in mente! Ma, con questo in mente, suppongo che debbano essere memorizzati solo il puntatore esatto dell'oggetto, forse l'offset, e il puntatore esatto della funzione. Questo rende totalmente inutili i thunk - penso - ma sono abbastanza sicuro che rimarranno i problemi di puntare alle funzioni membro e funzioni non membro.]

Il C ++ è già un grande linguaggio e l'aggiunta di questo avrebbe reso più grande. Quello che vuoi davvero è anche peggio di una semplice funzione membro associata, è qualcosa di più vicino a boost :: function. Si desidera archiviare sia void(*)() sia una coppia per i callback. Dopotutto, il motivo è che vuoi dare al chiamante un callback completo, e la persona che ha chiamato non dovrebbe preoccuparsi dei dettagli esatti.

La dimensione sarebbe probabilmente sizeof(void*)+sizeof(void(*)()). I puntatori alle funzioni dei membri possono essere più grandi, ma perché non sono associati. Devono affrontare la possibilità che tu stia prendendo l'indirizzo di una funzione virtuale, per esempio. Tuttavia, un tipo di funzione puntatore-membro-membro incorporato non soffrirebbe di questo sovraccarico. Può risolvere la funzione esatta da chiamare al momento dell'associazione.

Questo non è possibile con un UDT. boost :: function non può eliminare l'overhead di un PTMF quando associa il puntatore all'oggetto. Devi conoscere la struttura di una PTMF, una vtable, eccetera - tutte cose non standard. Tuttavia, potremmo arrivarci ora con C ++ 1x. Una volta che è in std ::, è un gioco equo per i venditori di compilatori. L'implementazione della libreria standard non è portatile (vedi ad esempio type_info).

Vorresti comunque avere una bella sintassi, immagino, anche se un fornitore di compilatori lo implementa nella libreria. Vorrei std::function<void(*)()> foo = &myX && X::bar. (Non si scontra con la sintassi esistente, poiché X :: bar non è un'espressione - solo & Amp; X :: bar è)

Mi viene in mente che i metodi hanno un argomento implicito this, quindi un c puntatore a un metodo non è sufficiente per consentire il metodo da chiamare (perché non c'è modo di determinare quale istanza dovrebbe essere usata per < => (o anche se qualsiasi è attualmente esistente)).

Modifica: Rocketmagnet commenta che ha affrontato questo problema nella domanda, e questo sembra essere il caso, anche se penso che sia stato aggiunto dopo aver iniziato questa risposta. ma dirò " mea culpa " ;, comunque.

Quindi permettimi di espandere un po 'il pensiero.

C ++ è strettamente correlato a c, e ha tutti i suoi tipi intrinseci compatibili con il linguaggio precedente (in gran parte a causa della storia dello sviluppo di c++, suppongo). Pertanto, un puntatore <=> intrinseco è un puntatore <=> ed è incapace di supportare l'uso richiesto.

Certamente potresti costruire un tipo derivato per fare il lavoro --- come nell'implementazione del boost --- ma una tale creatura appartiene a una libreria.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top