In una class derivata basata su modelli, perché è necessario qualificare i nomi dei membri della class base con “this->” all’interno di una funzione membro?

Mentre indago sul codice sorgente di Qt ho visto che i trolltech usano esplicitamente this parola chiave per accedere a un campo sul distruttore.

 inline ~QScopedPointer() { T *oldD = this->d; Cleanup::cleanup(oldD); this->d = 0; } 

Quindi, qual è il punto di questo utilizzo? Ci sono dei benefici?

Modifica: per coloro che votano per la chiusura di questa domanda, sospetto che questo utilizzo sia per alcuni casi di ereditarietà di class

Una parte della definizione della class QScopedPointer :

 template <typename T, typename Cleanup = QScopedPointerDeleter > class QScopedPointer 

Risposta C ++ (risposta generale)

Considera una class template Derived con una class base template:

 template  class Base { public: int d; }; template  class Derived : public Base { void f () { this->d = 0; } }; 

this ha tipo Derived , un tipo che dipende da T Quindi this ha un tipo dipendente. Quindi this->d rende d un nome dipendente. I nomi dipendenti vengono cercati nel contesto della definizione del modello come nomi non dipendenti e nel contesto dell’istanziazione.

Senza this-> , il nome d verrebbe cercato solo come nome non dipendente e non verrà trovato.

Un’altra soluzione è dichiarare d nella definizione del modello stessa:

 template  class Derived : public Base { using Base::d; void f () { d = 0; } }; 

Qanswer (risposta specifica)

d è un membro di QScopedPointer . Non è un membro ereditato. this-> non è necessario qui.

OTOH, QScopedArrayPointer è una class template ed è un membro ereditato di una class base template:

 template  > class QScopedArrayPointer : public QScopedPointer 

quindi this-> è necessario qui :

 inline T &operator[](int i) { return this->d[i]; } 

È facile vedere che è più facile mettere this-> dappertutto.

Comprendi la ragione

Suppongo che non sia chiaro a tutti gli utenti C ++ perché i nomi vengono cercati nelle classi base non dipendenti ma non nelle classi di base dipendenti:

 class Base0 { public: int nd; }; template  class Derived2 : public Base0, // non-dependent base public Base { // dependent base void f () { nd; // Base0::b d; // lookup of "d" finds nothing f (this); // lookup of "f" finds nothing // will find "f" later } }; 

C’è un motivo accanto a “lo standard dice così”: causa del nome modo vincolante nei modelli funziona.

I modelli possono avere un nome legato in ritardo, quando il modello viene istanziato: ad esempio f in f (this) . Al punto della Derived2::f() , non esiste alcuna variabile, funzione o tipo nome f conosciuta dal compilatore. L’insieme di quadro conosciute a cui f potrebbe fare riferimento è vuoto a questo punto. Questo non è un problema perché il compilatore sa che cercherà f più tardi come nome di una funzione, o come nome di una funzione modello.

OTOH, il compilatore non sa cosa fare con d ; non è un nome di funzione (chiamato). Non esiste un modo per bind in ritardo nomi di funzioni non (chiamati).

Ora, tutto ciò può sembrare una conoscenza elementare del polimorfismo del modello in fase di compilazione. La vera domanda sembra essere: perché non è legato a Base::d al momento della definizione del modello?

Il vero problema è che non c’è Base::d al momento della definizione del modello, perché non c’è un tipo completo Base in quel momento: Base è dichiarata, ma non definita! Potresti chiedere: che dire di questo:

 template  class Base { public: int d; }; 

sembra la definizione di un tipo completo!

In realtà, fino all’istanziazione, sembra più simile a:

 template  class Base; 

al compilatore. Un nome non può essere cercato in un modello di class! Ma solo in una specializzazione template (istanziazione). Il modello è una fabbrica per rendere la specializzazione dei modelli, un modello non è un insieme di specializzazione dei modelli . Il compilatore può cercare d in Base per ogni particolare tipo T , ma non può cercare d nel modello di class Base . Fino a quando non viene determinato un tipo T , Base::d rimane la Base::d astratta Base::d ; solo quando è noto il tipo T , Base::d inizia a riferirsi a una variabile di tipo int .

La conseguenza di ciò è che il modello di class Derived2 ha una base base completa Base0 ma una base di base incompleta (forward dichiarata). Solo per un tipo noto T , la “class template” (specializzazioni di un modello di class) Derived2 ha una class base completa, proprio come qualsiasi class normale.

Adesso vedi che:

 template  class Derived : public Base 

è in realtà un modello di specifiche della class base (una fabbrica per rendere specifiche della class base) che segue regole diverse da una specifica della class base all’interno di un modello.

Nota: il lettore potrebbe aver notato che ho inventato alcune frasi alla fine della spiegazione.

Questo è molto diverso: qui d è un nome qualificato in Derived , e Derived è dipendente poiché T è un parametro di modello. Un nome qualificato può essere tardivo anche se non è un nome di funzione (chiamato).

Un’altra soluzione è:

 template  class Derived : public Base { void f () { Derived::d = 0; // qualified name } }; 

Questo è equivalente.

Se pensi che all’interno della definizione di Derived , il trattamento di Derived come una class completa conosciuta a volte e come una class sconosciuta altre volte incoerente, beh, hai ragione.

Immagino che riguardi un uso eccessivo della routine Cleanup (). Il tipo che viene passato è esplicitamente controllato dal tipo di modello T, che a sua volta può controllare quale versione di Cleanup () sovraccaricata viene invocata.