Uso eccessivo di `this` in C ++

Ho a che fare con una grande base di codice che usa il seguente costrutto dappertutto

class MyClass { public: void f(int x); private: int x; }; void MyClass::f(int x) { ' ' this->x = x; ' ' } 

Personalmente, ho sempre usato e quindi preferisco il modulo

 class MyClass { public: void f(int x); private: int _x; }; void MyClass::f(int x) { ' ' _x = x; ' ' } 

I motivi per cui preferisco quest’ultimo sono che è più sintetico (meno codice = meno bug potenziali), e che non mi piace avere più variabili con lo stesso nome in ambito allo stesso tempo in cui posso evitarlo. Detto questo, sto vedendo il precedente utilizzo sempre più spesso in questi giorni. C’è qualche aspetto positivo del secondo approccio di cui non sono a conoscenza? (ad esempio effetto sul tempo di compilazione, uso con codice basato su modelli, ecc …) I vantaggi di entrambi gli approcci sono abbastanza significativi da giustificare un altro? Vi chiedo, anche se non mi piace il secondo approccio presente nel codice, la quantità di sforzo e il rischio associato di introdurre ulteriori bug non meritano assolutamente un refactoring.

La tua versione è un po ‘più pulita, ma mentre ci sei, vorrei:

  1. Evita il carattere di sottolineatura principale: _x è ok finché qualcuno non sceglie _MyField che è un nome riservato. Un carattere di sottolineatura iniziale seguito da una lettera maiuscola non è consentito come nome di variabile. Vedi: Quali sono le regole sull’uso di un carattere di sottolineatura in un identificatore C ++?
  2. Rendi privato o protetto l’attributo: la modifica è sicura se viene compilata e assicurerai che il tuo setter verrà utilizzato.
  3. Questa storia-> ha un uso, ad esempio nel codice basato su modelli, per rendere il nome del campo dipendente dal tuo tipo (può risolvere alcuni problemi di ricerca).

Un piccolo esempio di risoluzioni dei nomi che vengono risolti usando un esplicito this-> (testato con g ++ 3.4.3):

 #include  #include  class A { public: int g_; A() : g_(1) {} const char* f() { return __FUNCTION__; } }; const char* f() { return __FUNCTION__; } int g_ = -1; template < typename Base > struct Derived : public Base { void print_conflicts() { std::cout << f() << std::endl; // Calls ::f() std::cout << this->f() << std::endl; // Calls A::f() std::cout << g_ << std::endl; // Prints global g_ std::cout << this->g_ << std::endl; // Prints A::g_ } }; int main(int argc, char* argv[]) { Derived< A >().print_conflicts(); return EXIT_SUCCESS; } 

La denominazione dei campi non ha nulla a che fare con un codesmell. Come diceva Neil , la visibilità sul campo è l’unico codice valido qui.

Ci sono vari articoli riguardanti le convenzioni di denominazione in C ++:

  • convenzione di denominazione per variabile pubblica e privata?
  • Convenzione di denominazione dei metodi privata
  • c ++ utilizzo dello spazio dei nomi e regole di denominazione

eccetera.

Questo uso di “this” è incoraggiato dagli standard di codifica Microsoft C #. Fornisce una buona chiarezza del codice e intende essere uno standard sull’uso di m_ o _ o qualsiasi altra cosa nelle variabili membro.

Sinceramente, non mi piace affatto il carattere di sottolineatura nei nomi comunque, ho usato come prefisso tutti i miei membri con una sola ‘m’.

Un sacco di persone lo usano perché nel loro IDE farà una lista di identificatori della class corrente pop-up.

So che lo faccio in BCB.

Penso che l’esempio fornito con il conflitto di denominazione sia un’eccezione. In Delphi però, le linee guida di stile usano un prefisso (di solito “a”) per i parametri per evitare esattamente questo.

La mia sensazione personale è che combattere una convenzione di codifica esistente è qualcosa che non dovresti fare. Come Sutter / Alexandrescu mette nel loro libro ‘Convenzioni di codifica C ++’: non preoccuparti delle piccole cose. Chiunque è in grado di leggere l’uno o l’altro, indipendentemente dal fatto che sia presente un “questo->” o “_” o altro.

Tuttavia, la coerenza delle convenzioni di denominazione è qualcosa che in genere si desidera, quindi attenersi a una convenzione in un determinato ambito (almeno lo scopo del file, idealmente l’intero codice base, ovviamente) è considerato una buona pratica. Hai detto che questo stile è usato su una base di codice più ampia, quindi penso che riadattare un’altra convenzione sarebbe piuttosto una ctriggers idea.

Se, dopo tutto, trovi che c’è una buona ragione per cambiarlo, non farlo manualmente. Nel migliore dei casi, il tuo IDE supporta questo tipo di “refactoring”. Altrimenti, scrivi uno script per cambiarlo. Cerca e sostituisci dovrebbe essere l’ultima opzione. In ogni caso, è necessario disporre di un backup (controllo del codice sorgente) e di un qualche tipo di impianto di prova automatizzato. Altrimenti non ti divertirai.

L’uso di “questo” in questo modo è IMO, non un odore di codice, ma è semplicemente una preferenza personale. Non è quindi importante quanto la coerenza con il resto del codice nel sistema. Se questo codice è incoerente puoi cambiarlo in modo che corrisponda all’altro codice. Se cambiandolo, introdurrai un’incoerenza con la maggior parte del resto del codice, che è molto brutto e lo lascerei in pace.

Non vuoi mai entrare in una posizione di giocare a tennis in codice, dove qualcuno cambia qualcosa puramente per farlo apparire “bello” solo per qualcun altro che viene in seguito con gusti diversi che poi lo cambia.

 class MyClass{ public: int x; void f(int xval); }; // void MyClass::f(int xval){ x = xval; } 

Io uso sempre la convenzione di denominazione m_ . Anche se non mi piace la “notazione ungherese” in generale, trovo molto utile vedere molto chiaramente se sto lavorando con i dati dei membri della class. Inoltre, ho trovato che usando 2 nomi di variabili identici nello stesso campo di applicazione troppo incline agli errori.

Sono d’accordo. Non mi piace la convenzione di denominazione, preferisco quella in cui esiste un’ovvia distinzione tra variabili membro e variabili locali. Cosa succede se si lascia fuori this ?

Secondo me questo tende ad aggiungere clutter al codice, quindi tendo ad usare nomi di variabili diversi (a seconda della convenzione, potrebbe trattarsi di un trattino basso, m_ , qualunque cosa).

 class MyClass { public: int m_x; void f(int p_x); }; void MyClass::f(int p_x) { m_x = p_x; } 

… è il mio modo preferito di utilizzare prefissi di ambito. m_ per membro, p_ per parametro (alcuni usano a_ per argomento invece), g_ per globale e talvolta l_ per locale se aiuta la leggibilità.

Se hai due variabili che meritano lo stesso nome, questo può aiutare molto ed evitare di dover apportare qualche variazione casuale al suo significato solo per evitare la ridefinizione. O ancora peggio, il temuto ‘x2, x3, x4, ecc’ …

È più normale in C ++ che i membri vengano inizializzati sulla costruzione usando l’inizializzatore.

Per fare ciò, devi usare un nome diverso per il nome della variabile membro.

Quindi, anche se Foo(int x) { this.x = x; } Foo(int x) { this.x = x; } in Java, non lo farei in C ++.

Il vero odore potrebbe essere la mancanza di uso di inizializzatori e metodi che non fanno nient’altro che variabili mutanti dei membri, piuttosto che l’uso di this -> x stesso.

Qualcuno sa perché è una pratica universale in ogni negozio C ++ in cui sono stato usato per usare nomi diversi per gli argomenti del costruttore per le variabili membro quando si usano gli inizializzatori? c’erano alcuni compilatori C ++ che non lo supportavano?

Oggi, la maggior parte degli editor IDE colorano le variabili per indicare i membri della class delle variabili locali. Pertanto, IMO, né i prefissi né “questo->” dovrebbero essere richiesti per la leggibilità.

Non mi piace usare “questo” perché è atavico. Se stai programmando nel buon vecchio C (ricorda C?), E vuoi imitare alcune delle caratteristiche di OOP, crei una struttura con diversi membri (sono simili alle proprietà del tuo object) e crei un set di funzioni che puntano tutte a quella struttura come primo argomento (sono analoghe ai metodi di quell’object).

(Penso che questa syntax typedef sia corretta ma è passato un po ‘di tempo …)

 typedef struct _myclass { int _x; } MyClass; void f(MyClass this, int x) { this->_x = x; } 

In effetti, credo che i compilatori C ++ più vecchi compilerebbero effettivamente il codice con il modulo sopra e poi lo passerebbero al compilatore C. In altre parole, il C ++ in una certa misura era solo zucchero sintattico. Quindi non sono sicuro del motivo per cui qualcuno vorrebbe programmare in C ++ e tornare ad usare esplicitamente “questo” nel codice – forse è “sintisweet sintattico”

Se hai un problema con le convenzioni sui nomi puoi provare ad usare qualcosa come il folowing.

 class tea { public: int cup; int spoon; tea(int cups, int spoons); }; 

o

 class tea { public: int cup; int spoon; tea(int drink, int sugar); }; 

Penso che tu abbia avuto l’idea. Fondamentalmente sta nominando le variabili diverse ma “uguali” in senso logico. Spero possa essere d’aiuto.