2013-08-21 11 views
16

Ho recentemente sono imbattuto in questo strano funzione in qualche classe:Il puntatore "questo" può essere diverso dal puntatore dell'oggetto?

void* getThis() {return this;} 

E più tardi nel codice a volte è usato in questo modo: bla->getThis() (Dove bla è un puntatore ad un oggetto della classe in cui questa funzione è definita.) E non riesco a capire cosa possa essere utile. C'è qualche situazione in cui un puntatore a un oggetto sarebbe diverso da quello dell'oggetto this (dove bla != bla->getThis())?

Sembra una domanda stupida, ma mi chiedo se mi manca qualcosa qui ..

+0

Sono presenti modelli? O ereditarietà? Il tipo restituito è dello stesso tipo della classe che contiene la funzione? Perché altrimenti non ha assolutamente senso –

+0

In quale contesto viene chiamato 'getThis'? – Joni

+6

sembra come se fosse per il cast di 'void *'. Perché non si sono limitati a "annullare" * Non ne ho idea. Vedi alcune cose divertenti nel codice di altre persone. – Dave

risposta

17

Ovviamente i valori del puntatore possono essere diversi! Di seguito un esempio che dimostra il problema (potrebbe essere necessario utilizzare derived1 sul sistema anziché derived2 per ottenere una differenza). Il punto è che il puntatore this viene in genere regolato quando è coinvolta l'ereditarietà multipla virtuale. Questo potrebbe essere un caso raro, ma succede.

Un potenziale caso d'uso di questo linguaggio è quello di essere in grado di ripristinare gli oggetti di tipo noto, dopo la loro memorizzazione come void const* (o void*; la correttezza const non importa qui): se si dispone di una gerarchia di ereditarietà complesso, è non può semplicemente lanciare un qualsiasi puntatore dispari a un void* e sperare di poterlo ripristinare al suo tipo originale! Cioè, per ottenere facilmente, ad esempio, un puntatore a base (dall'esempio seguente) e convertirlo in void*, si chiamerebbe p->getThis() che è molto più semplice da static_cast<base*>(p) e si ottiene un void* che può essere trasmesso in modo sicuro a base* utilizzando a static_cast<base*>(v): è possibile invertire la conversione implicita ma solo se si esegue il cast del tipo esatto da cui proviene il puntatore originale. Vale a dire, static_cast<base*>(static_cast<void*>(d)) dove d è un puntatore a un oggetto di un tipo derivato da base illegale ma static_cast<base*>(d->getThis()) è legale.

Ora, perché l'indirizzo cambia in primo luogo? Nell'esempio base è una classe base virtuale di due classi derivate ma potrebbero essercene di più. Tutti gli oggetti secondari la cui classe eredita virtualmente da base condivideranno un oggetto comune base in un oggetto di un'altra classe derivata (concrete nell'esempio seguente). La posizione di questo suboggetti base può essere diversa rispetto al rispettivo sottobooggetto derivato, a seconda di come sono ordinate le diverse classi. Di conseguenza, il puntatore all'oggetto base è in genere diverso dai puntatori ai sottooggetti di classi che ereditano virtualmente da base. L'offset pertinente verrà calcolato al momento della compilazione, quando possibile, o provenire da qualcosa come un vtable in fase di esecuzione. Gli offset vengono regolati durante la conversione dei puntatori lungo la gerarchia di ereditarietà.

#include <iostream> 

struct base 
{ 
    void const* getThis() const { return this; } 
}; 

struct derived1 
    : virtual base 
{ 
    int a; 
}; 

struct derived2 
    : virtual base 
{ 
    int b; 
}; 

struct concrete 
    : derived1 
    , derived2 
{ 
}; 

int main() 
{ 
    concrete c; 
    derived2* d2 = &c; 
    void const* dptr = d2; 
    void const* gptr = d2->getThis(); 
    std::cout << "dptr=" << dptr << " gptr=" << gptr << '\n'; 
} 
+1

ooh. Potrebbe essere una voce per un concorso di offuscamento del codice! – Dave

+2

@Dave: Come Steve Clamage ha giustamente sottolineato molto tempo fa, "un concorso C++ offuscato sarebbe come sparare a un pesce in un barile"! C'è un vero uso di questa conversione esplicita, però: puoi lanciare il puntatore ottenuto da 'getThis()' a un 'base const *'. Non si può fare lo stesso con un puntatore ottenuto da 'd2' convertendolo in' void const * '(si potrebbe lanciare un puntatore così ottenuto a un' derivata2 const * ', tuttavia, cioè, annullare la conversione implicita). –

+0

+1 perché l'eredità multipla è l'unico scenario a cui riesco a pensare dove questo potrebbe avere senso. Ancora non riesco a pensare ad un uso pratico, ma se c'è una cosa *, so davvero che non so nulla. :) – syam

2

n Sì, in circostanze limitate.

Sembra che si tratti di qualcosa ispirato a Smalltalk, in cui tutti gli oggetti hanno un metodo yourself. Ci sono probabilmente alcune situazioni in cui questo rende il codice più pulito. Come notano i commenti, questo sembra un modo strano per implementare anche questo idioma in C++.

Nel tuo caso specifico, vorrei grep per usi effettivi del metodo per vedere come viene utilizzato.

+0

+1 solo per la parte 'grep'. Onestamente, in 20+ anni di C++ non ho mai visto una situazione in cui questo costrutto sarebbe stato necessario, ma forse sono solo io ... – syam

+0

@syam Non riesco a pensare a un'istanza, ma la mia esperienza con smalltalk è limitata. Presumo che sia diventato una cosa in quel linguaggio per una ragione, che può o non può applicarsi in C++. – Marcin

+0

Con 'void * p0 = bla; void * p1 = bla-> getThis(); 'è possibile che' p0! = p1'! Tutto ciò che richiede è che 'bla' sia un puntatore a un tipo opportunamente costruito. –

0

Mi sono imbattuto in qualcosa come molti (molti molti) anni fa. Se ricordo correttamente, era necessario quando una classe sta manipolando altre istanze della stessa classe. Un esempio potrebbe essere una classe contenitore che può contenere il proprio tipo/(classe?).

0

Questo potrebbe essere un modo per ignorare questa parola chiave. Diciamo che hai un pool di memoria, completamente inizializzato all'inizio del tuo programma, ad esempio sai che in qualsiasi momento puoi gestire un massimo di 50 messaggi, CMessage. Si crea un pool delle dimensioni di 50 * sizeof (CMessage) (cosa potrebbe mai essere questa classe) e CMessage implementa la funzione getThis.

In questo modo, invece di sostituire la nuova parola chiave, basta sovrascrivere il "questo", accedendo al pool. Può anche significare che l'oggetto potrebbe essere definito su diversi spazi di memoria, diciamo su una SRAM, in modalità di avvio, e quindi su una SDRAM.

Potrebbe essere che la stessa istanza restituirà valori diversi per getThis attraverso il programma in una situazione del genere, ovviamente, ovviamente, quando sovrasta.

1

La classe può essere personalizzata operator& (quindi &a non può restituire this di a). Ecco perché esiste std::addressof.

Problemi correlati