Perché usare i prefissi sulle variabili membro nelle classi C ++


151

Un sacco di codice C ++ utilizza convenzioni sintattiche per contrassegnare le variabili dei membri. Esempi comuni includono

  • m_ memberName per membri pubblici (in cui i membri pubblici sono utilizzati affatto)
  • _ memberName per membri privati ​​o tutti i membri

Altri tentano di imporre l'uso di questo-> membro ogni volta che viene utilizzata una variabile membro.

Nella mia esperienza, le basi di codice più grandi non riescono ad applicare tali regole in modo coerente.

In altre lingue, queste convenzioni sono molto meno diffuse. Lo vedo solo occasionalmente in codice Java o C #. Penso di non averlo mai visto in codice Ruby o Python. Pertanto, sembra esserci una tendenza con linguaggi più moderni a non utilizzare un markup speciale per le variabili membro.

Questa convenzione è ancora utile oggi in C ++ o è solo un anacronismo? Soprattutto perché viene utilizzato in modo incoerente tra le librerie. Le altre lingue non hanno dimostrato che si può fare a meno dei prefissi dei membri?


15
Lo preferisco; in basi di codice complesse può essere importante sapere quali variabili sono locali e quali no. In genere uso il prefisso per forzare questo-> che trovo un sacco di digitazione extra e opzionale (mentre la denominazione ti costringerà a farlo)
Joe

5
Non l'hai mai visto in Ruby a causa dell'attributo @ for e del linguaggio di generazione degli accessi rispetto all'utilizzo diretto degli attributi.
Steve Jessop,

6
Secondo PEP 8 , le variabili dei membri non pubblici devono essere precedute da un trattino basso in Python (esempio:) self._something = 1.
Nathan Osman

2
L'evidenziazione della sintassi dell'editor non dovrebbe essere utilizzata per identificarli?
troppo

2
Hai visto l'equivalente di this->membernel codice Python. In Python sarebbe in genere self.membere non è solo una convenzione, è richiesto dal linguaggio.
matec

Risposte:


49

Devi stare attento con l'uso di un trattino basso. Un trattino basso prima di una lettera maiuscola in una parola è riservato. Per esempio:

_foo

_L

sono tutte parole riservate mentre

_foo

_L

non sono. Vi sono altre situazioni in cui i caratteri di sottolineatura iniziali prima delle lettere minuscole non sono consentiti. Nel mio caso specifico, ho scoperto che la _L era riservata a Visual C ++ 2005 e lo scontro ha creato alcuni risultati inaspettati.

Sono sulla soglia di quanto sia utile contrassegnare le variabili locali.

Ecco un link su quali identificatori sono riservati: Quali sono le regole sull'uso di un trattino basso in un identificatore C ++?


4
In realtà, sia _foo che _l sono riservati nell'ambito dello spazio dei nomi.

13
Ma sono ok come nomi di variabili membro. Non faccio prefisso i trattini bassi, perché le regole sono troppo confuse e in passato mi ero bruciato.
Juan,

11
Queste non sono parole riservate. Sono nomi riservati. Se fossero parole riservate, non potresti usarle affatto. Poiché sono nomi riservati, puoi usarli, ma a tuo rischio e pericolo.
TonyK,

236

Sono tutti a favore dei prefissi fatti bene .

Penso che la notazione ungherese (di sistema) sia responsabile della maggior parte del "cattivo rap" che i prefissi ottengono.

Questa notazione è in gran parte inutile nei linguaggi fortemente tipizzati, ad es. In C ++ "lpsz" per dirti che la tua stringa è un puntatore lungo a una stringa nul terminata, quando: architettura segmentata è storia antica, le stringhe C ++ sono da comuni puntatori convenzionali a nul-terminato array di caratteri, e non è poi così difficile sapere che "customerName" è una stringa!

Tuttavia, utilizzo i prefissi per specificare l' utilizzo di una variabile (essenzialmente "App ungherese", anche se preferisco evitare il termine ungherese a causa della sua cattiva e ingiusta associazione con il sistema ungherese), e questo è un risparmio di tempo molto utile e approccio per la riduzione dei bug .

Io uso:

  • m per i membri
  • c per costanti / readonlys
  • p per puntatore (e pp per puntatore a puntatore)
  • v per volatile
  • s per statico
  • i per indici e iteratori
  • e per eventi

Laddove desidero chiarire il tipo , utilizzo i suffissi standard (ad esempio Elenco, ComboBox, ecc.).

Questo rende il programmatore consapevole dell'uso della variabile ogni volta che la vede / la usa. Probabilmente il caso più importante è "p" per il puntatore (perché l'uso cambia da var. A var-> e bisogna stare molto più attenti con i puntatori - NULL, aritmetica dei puntatori, ecc.), Ma tutti gli altri sono molto utili.

Ad esempio, puoi utilizzare lo stesso nome di variabile in più modi in una singola funzione: (qui un esempio C ++, ma si applica ugualmente a molte lingue)

MyClass::MyClass(int numItems)
{
    mNumItems = numItems;
    for (int iItem = 0; iItem < mNumItems; iItem++)
    {
        Item *pItem = new Item();
        itemList[iItem] = pItem;
    }
}

Puoi vedere qui:

  • Nessuna confusione tra membro e parametro
  • Nessuna confusione tra indice / iteratore ed elementi
  • Utilizzo di un insieme di variabili chiaramente correlate (elenco di elementi, puntatore e indice) che evitano le numerose insidie ​​di nomi generici (vaghi) come "count", "index".
  • I prefissi riducono la digitazione (più breve e funzionano meglio con il completamento automatico) rispetto a alternative come "itemIndex" e "itemPtr"

Un altro grande punto degli iteratori di "iName" è che non indicizzo mai un array con l'indice errato e se copio un ciclo all'interno di un altro ciclo non devo refactificare una delle variabili dell'indice del ciclo.

Confronta questo esempio irrealisticamente semplice:

for (int i = 0; i < 100; i++)
    for (int j = 0; j < 5; j++)
        list[i].score += other[j].score;

(che è difficile da leggere e spesso porta all'uso di "i" dove "j" era inteso)

con:

for (int iCompany = 0; iCompany < numCompanies; iCompany++)
    for (int iUser = 0; iUser < numUsers; iUser++)
       companyList[iCompany].score += userList[iUser].score;

(che è molto più leggibile e rimuove tutta la confusione sull'indicizzazione. Con il completamento automatico negli IDE moderni, questo è anche veloce e facile da digitare)

Il prossimo vantaggio è che i frammenti di codice non richiedono alcun contesto per essere compreso. Posso copiare due righe di codice in un'e-mail o in un documento e chiunque legga quel frammento può dire la differenza tra tutti i membri, costanti, puntatori, indici, ecc. Non devo aggiungere "oh, e stai attento perché "data" è un puntatore a un puntatore ", perché si chiama 'ppData'.

E per lo stesso motivo, non devo spostare gli occhi da una riga di codice per capirlo. Non devo cercare nel codice per scoprire se "dati" è un locale, un parametro, un membro o una costante. Non devo spostare la mano sul mouse, quindi posso passare il puntatore su "dati" e quindi attendere che venga visualizzata una descrizione comandi (che a volte non viene mai visualizzata). In questo modo i programmatori possono leggere e comprendere il codice in modo significativamente più veloce, perché non perdono tempo a cercare su e giù o ad aspettare.

(Se non pensi di perdere tempo a cercare su e giù per elaborare le cose, trova un po 'di codice che hai scritto un anno fa e che non hai mai visto da allora. Apri il file e salta a metà circa senza leggerlo. Scopri come lontano puoi leggere da questo punto prima di non sapere se qualcosa è un membro, un parametro o un locale. Ora passa a un'altra posizione casuale ... Questo è ciò che facciamo tutti tutto il giorno quando passiamo attraverso il codice di qualcun altro o cercando di capire come chiamare la loro funzione)

Il prefisso 'm' evita anche la brutta e prolissa notazione "this->" e l'incoerenza che garantisce (anche se stai attento di solito finirai con una miscela di "this-> data" e "dati" nella stessa classe, perché nulla impone una ortografia coerente del nome).

la notazione "questa" ha lo scopo di risolvere l' ambiguità , ma perché qualcuno dovrebbe scrivere deliberatamente un codice che può essere ambiguo? L'ambiguità sarà portare ad un bug, prima o poi. E in alcune lingue "questo" non può essere utilizzato per i membri statici, quindi è necessario introdurre "casi speciali" nel proprio stile di codifica. Preferisco avere un'unica semplice regola di codifica che si applica ovunque: esplicita, inequivocabile e coerente.

L'ultimo grande vantaggio è con Intellisense e il completamento automatico. Prova a utilizzare Intellisense su un Windows Form per trovare un evento: devi scorrere centinaia di misteriosi metodi di classe base che non dovrai mai chiamare per trovare gli eventi. Ma se ogni evento avesse un prefisso "e", verrebbero automaticamente elencati in un gruppo sotto "e". Pertanto, il prefisso funziona per raggruppare i membri, i cons, gli eventi, ecc. Nell'elenco intellisense, rendendo molto più semplice e veloce trovare i nomi desiderati. (Di solito, un metodo potrebbe avere circa 20-50 valori (locali, parametri, membri, cons, eventi) che sono accessibili nel suo ambito. Ma dopo aver digitato il prefisso (voglio usare un indice ora, quindi digito 'i. .. '), mi vengono presentate solo 2-5 opzioni di completamento automatico.

Sono un programmatore pigro e la convenzione di cui sopra mi fa risparmiare molto lavoro. Posso programmare più velocemente e commetto molti meno errori perché so come utilizzare ogni variabile.


Argomenti contro

Quindi, quali sono i contro? Argomenti tipici contro i prefissi sono:

  • "Gli schemi dei prefissi sono cattivi / cattivi" . Sono d'accordo che "m_lpsz" e il suo genere sono scarsamente pensati e del tutto inutili. Ecco perché ti consiglierei di usare una notazione ben progettata progettata per supportare le tue esigenze, piuttosto che copiare qualcosa di inappropriato per il tuo contesto. (Usa lo strumento giusto per il lavoro).

  • "Se cambio l'uso di qualcosa devo rinominarlo" . Sì, certo che lo fai, questo è il refactoring e perché gli IDE hanno strumenti di refactoring per svolgere questo lavoro in modo rapido e indolore. Anche senza prefissi, cambiare l'utilizzo di una variabile quasi sicuramente significa che il suo nome dovrebbe essere cambiato.

  • "I prefissi mi confondono e basta" . Come ogni strumento fino a quando non impari come usarlo. Una volta che il tuo cervello si è abituato ai modelli di denominazione, filtrerà automaticamente le informazioni e non ti dispiacerà davvero che i prefissi siano più lì. Ma devi usare uno schema come questo solidamente per una settimana o due prima di diventare davvero "fluente". Ed è allora che molte persone guardano il vecchio codice e iniziano a chiedersi come siano mai riuscite senza un buon schema di prefissi.

  • "Posso solo guardare il codice per elaborare questa roba" . Sì, ma non è necessario perdere tempo a cercare altrove nel codice o a ricordare ogni piccolo dettaglio di esso quando la risposta è proprio nel punto in cui l'occhio è già focalizzato.

  • (Alcune) di tali informazioni possono essere trovate semplicemente aspettando che venga visualizzata una descrizione comandi sulla mia variabile . Sì. Se supportato, per alcuni tipi di prefisso, quando il codice viene compilato in modo pulito, dopo un'attesa, è possibile leggere una descrizione e trovare le informazioni che il prefisso avrebbe trasmesso all'istante. Ritengo che il prefisso sia un approccio più semplice, più affidabile e più efficiente.

  • "Sta scrivendo di più" . Veramente? Un altro personaggio in più? O lo è - con gli strumenti di completamento automatico IDE, spesso ridurrà la digitazione, poiché ogni carattere prefisso restringe significativamente lo spazio di ricerca. Premi "e" e i tre eventi della tua classe compaiono in intellisense. Premere "c" per elencare le cinque costanti.

  • "Posso usare this->invece di m" . Bene, sì, puoi. Ma questo è solo un prefisso molto più brutto e più prolisso! Solo comporta un rischio molto maggiore (specialmente nei team) perché per il compilatore è facoltativo e quindi il suo utilizzo è spesso incoerente. md'altra parte è breve, chiaro, esplicito e non facoltativo, quindi è molto più difficile commettere errori nell'usarlo.


6
Voglio dire che ho letto che il problema con Hungarien Notation è appena stato causato dall'incomprensione di Simonyi. Ha scritto un prefisso che dovrebbe essere usato per indicare il tipo di una variabile in cui intendeva "tipo" come in "tipo di cosa", non un tipo di dati letterale. Più tardi i ragazzi della piattaforma di Microsoft lo hanno raccolto e hanno inventato lpsz ... e il resto è storia ...
VoidPointer

19
"s è per statico" suona praticamente come la "cattiva" forma ungherese per me.
jalf

6
@Mehrdad: non credo zsia molto spesso utile in un linguaggio come C ++ in cui quel tipo di dettagli di implementazione di basso livello dovrebbero essere incapsulati in una classe, ma in C (dove la terminazione zero è una distinzione importante) sono d'accordo con te. IMO qualsiasi schema che utilizziamo dovrebbe essere adattato come richiesto per soddisfare al meglio le nostre esigenze - Quindi, se la terminazione zero influisce sull'uso della variabile, non c'è nulla di sbagliato nel dichiarare "z" un prefisso utile.
Jason Williams,

14
The most important case is "p" for pointer (because the usage changes from var. to var-> and you have to be much more careful with pointers.Sono completamente in disaccordo. Se uso un puntatore sbagliato, semplicemente non verrà compilato ( void*potrebbe essere un'eccezione per i doppi puntatori). E l'intero ->corso .è sufficiente a dirmi che è un puntatore. Inoltre, se stai utilizzando il completamento automatico, il tuo editor probabilmente ha dei suggerimenti per le dichiarazioni, eliminando la necessità di aggiungere un prefisso per le informazioni variabili. Indipendentemente da ciò, buona risposta.
Thomas Eding,

5
È stato votato per le spiegazioni chiare, complete e interessanti, tuttavia c'è poco qui che suggerisce come questo risparmi tempo in C ++ ANCORA rimane in gran parte inutilizzato in molte altre lingue.

115

In genere non uso un prefisso per le variabili membro.

Ho usato per usare un mprefisso, fino a quando qualcuno ha sottolineato che "C ++ ha già un prefisso standard per l'accesso utente: this->.

Quindi è quello che uso ora. Cioè, quando c'è ambiguità , aggiungo il this->prefisso, ma di solito non esiste alcuna ambiguità e posso semplicemente fare riferimento direttamente al nome della variabile.

Per me, questo è il migliore dei due mondi. Ho un prefisso che posso usare quando ne ho bisogno e sono libero di lasciarlo fuori quando possibile.

Ovviamente, l'ovvio contrario a questo è "sì, ma poi non puoi vedere a colpo d'occhio se una variabile è o meno un membro della classe".

Al che dico "e allora? Se hai bisogno di saperlo, la tua classe probabilmente ha troppo stato. O la funzione è troppo grande e complicata".

In pratica, ho scoperto che funziona molto bene. Come bonus aggiuntivo mi permette di promuovere facilmente una variabile locale a un membro della classe (o viceversa), senza doverlo rinominare.

E soprattutto, è coerente! Non devo fare nulla di speciale o ricordare convenzioni per mantenere la coerenza.


A proposito, non dovresti usare i trattini bassi di punta per i membri della tua classe. Ti avvicini scomodamente ai nomi riservati dall'implementazione.

Lo standard riserva tutti i nomi che iniziano con il doppio trattino basso o trattino basso seguito dalla lettera maiuscola. Si riserva inoltre tutti i nomi che iniziano con un singolo trattino basso nello spazio dei nomi globale .

Quindi un membro della classe con un carattere di sottolineatura iniziale seguito da una lettera minuscola è legale, ma prima o poi farai lo stesso con un identificatore che inizia con una maiuscola, o altrimenti infrangi una delle regole di cui sopra.

Quindi è più facile evitare solo i trattini bassi. Utilizzare un carattere di sottolineatura postfisso o un prefisso m_o solo mse si desidera codificare l'ambito nel nome della variabile.


"Quindi un membro della classe con un carattere di sottolineatura iniziale seguito da una lettera minuscola è legale, ma prima o poi farai lo stesso con un identificatore che inizia con una lettera maiuscola o altrimenti infrangi una delle regole di cui sopra." - le variabili dei membri della classe non si trovano nello spazio dei nomi globale, quindi un carattere di sottolineatura iniziale è sicuro, indipendentemente dal fatto che sia seguito da una lettera inferiore o maiuscola.
mbarnett,

3
@mbarnett: No, il carattere di sottolineatura seguito da lettere maiuscole è riservato in generale , non solo nello spazio dei nomi globale.
jalf

9
sorpreso dal fatto che il voto di questa risposta sia inferiore a quello del prefisso.
Marson Mao,

Sono d'accordo con questa risposta, basta usare this->se è necessario specificare che è una variabile membro o no, va bene lo stesso.
David Morton,

Inoltre, non è necessario documentare la convenzione per fornire il codice ad altre persone. Tutti capiscono cosa this->significa.
Caduchon,

34

Preferisco le sottolineature postfix, come queste:

class Foo
{
   private:
      int bar_;

   public:
      int bar() { return bar_; }
};

Anch'io. Dò anche lo stesso nome agli accessori / ai mutatori.
Rob

4
Interessante. All'inizio sembra un po 'brutto, ma posso vedere come può essere utile.
ya23

6
Direi che è molto meno brutto di: "mBar" o "m_bar".
Sidan,

6
ma poi hai vector<int> v_;e anche scrivere v_.push_back(5)è abbastanza brutto
avim

4
Questo è lo stile di Google C ++.
Justme0,

20

Ultimamente ho avuto la tendenza a preferire il prefisso m_ invece di non avere alcun prefisso, i motivi non sono tanto importanti da contrassegnare le variabili dei membri, ma evitano l'ambiguità, diciamo che hai un codice come:

void set_foo(int foo) { foo = foo; }

Quello della causa non funziona, solo uno è foopermesso. Quindi le tue opzioni sono:

  • this->foo = foo;

    Non mi piace, poiché provoca l'ombreggiatura dei parametri, non è più possibile utilizzare gli g++ -Wshadowavvisi, ma è anche più lungo da digitare m_. Si verificano anche conflitti di denominazione tra variabili e funzioni quando si hanno a int foo;e a int foo();.

  • foo = foo_; o foo = arg_foo;

    Lo sto usando da un po ', ma rende gli elenchi di argomenti brutti, la documentazione non dovrebbe avere a che fare con la disambiguità dei nomi nell'implementazione. Esistono anche conflitti di denominazione tra variabili e funzioni.

  • m_foo = foo;

    La documentazione dell'API rimane pulita, non si ottiene ambiguità tra le funzioni e le variabili dei membri e quindi è più breve da digitare this->. L'unico svantaggio è che rende brutte le strutture POD, ma poiché le strutture POD non soffrono in primo luogo dell'ambiguità del nome, non è necessario usarle con esse. Avere un prefisso univoco semplifica anche alcune operazioni di ricerca e sostituzione.

  • foo_ = foo;

    La maggior parte dei vantaggi di m_applicare, ma lo rifiuto per motivi estetici, un carattere di sottolineatura finale o iniziale rende la variabile sembrare incompleta e sbilanciata. m_sembra solo migliore. L'uso m_è anche più estensibile, in quanto è possibile utilizzarlo g_per i globali e s_per la statica.

PS: Il motivo per cui non vedi m_in Python o Ruby è perché entrambi i linguaggi applicano il proprio prefisso, Ruby utilizza @per le variabili membro e Python richiede self..


1
per essere onesti, hai perso almeno altre 2 opzioni, ad esempio (a) usa nomi completi come foosolo per i membri e invece usa nomi a lettera singola o abbreviati per parametri o altri locali / usa e getta, come int f; o (b) prefissare i parametri o altri locali con qualcosa. buon punto re m_e baccelli, però; Sono arrivato indipendentemente alla preferenza di seguire entrambe queste linee guida, per la maggior parte.
underscore_d

12

Quando si legge una funzione membro, sapere chi "possiede" ogni variabile è assolutamente essenziale per comprendere il significato della variabile. In una funzione come questa:

void Foo::bar( int apples )
{
    int bananas = apples + grapes;
    melons = grapes * bananas;
    spuds += melons;
}

... è abbastanza facile vedere da dove provengono le mele e le banane, ma che dire dell'uva, dei meloni e degli spud? Dovremmo cercare nello spazio dei nomi globale? Nella dichiarazione di classe? La variabile è un membro di questo oggetto o un membro della classe di questo oggetto? Senza conoscere la risposta a queste domande, non puoi capire il codice. E in una funzione più lunga, anche le dichiarazioni di variabili locali come mele e banane possono perdersi nel riordino.

Preparare un'etichetta coerente per globali, variabili membro e variabili membro statiche (forse rispettivamente g_, m_ e s_) chiarisce immediatamente la situazione.

void Foo::bar( int apples )
{
    int bananas = apples + g_grapes;
    m_melons = g_grapes * bananas;
    s_spuds += m_melons;
}

All'inizio potrebbe volerci un po 'di tempo per abituarsi, ma poi cosa succede nella programmazione? C'è stato un giorno in cui anche {e} ti è sembrato strano. E una volta che ti ci abitui, ti aiutano a capire il codice molto più rapidamente.

(Usare "this->" al posto di m_ ha senso, ma è ancora più prolisso e visivamente distruttivo. Non lo vedo come una buona alternativa per contrassegnare tutti gli usi delle variabili membro.)

Una possibile obiezione all'argomento di cui sopra sarebbe quella di estendere l'argomento ai tipi. Potrebbe anche essere vero che conoscere il tipo di una variabile "è assolutamente essenziale per comprendere il significato della variabile". In tal caso, perché non aggiungere un prefisso a ciascun nome di variabile che ne identifica il tipo? Con questa logica, si finisce con la notazione ungherese. Ma molte persone trovano la notazione ungherese laboriosa, brutta e inutile.

void Foo::bar( int iApples )
{
    int iBananas = iApples + g_fGrapes;
    m_fMelons = g_fGrapes * iBananas;
    s_dSpuds += m_fMelons;
}

ungherese faraccontaci qualcosa di nuovo sul codice. Ora capiamo che ci sono diversi cast impliciti nella funzione Foo :: bar (). Il problema con il codice ora è che il valore delle informazioni aggiunte dai prefissi ungheresi è piccolo rispetto al costo visivo. Il sistema di tipi C ++ include molte funzionalità per aiutare i tipi a lavorare bene insieme o per generare un avviso o un errore del compilatore. Il compilatore ci aiuta a gestire i tipi: non abbiamo bisogno di notazione per farlo. Possiamo dedurre abbastanza facilmente che le variabili in Foo :: bar () sono probabilmente numeriche, e se questo è tutto ciò che sappiamo, è abbastanza buono per ottenere una comprensione generale della funzione. Pertanto, il valore della conoscenza del tipo preciso di ciascuna variabile è relativamente basso. Eppure la bruttezza di una variabile come "s_dSpuds" (o anche solo "dSpuds") è grande. Così,


Grazie per l'idea s_. Sembra molto utile e in qualche modo non mi era mai venuto in mente.
Chris Olsen,

10

Non posso dire quanto sia ampia, ma parlando personalmente, ho sempre (e ho sempre) anteposto le mie variabili membro con 'm'. Per esempio:

class Person {
   .... 
   private:
       std::string mName;
};

È l'unica forma di prefisso che uso (sono una notazione molto ungherese), ma mi ha permesso di mantenermi in forma nel corso degli anni. Per inciso, in genere detesto l'uso di caratteri di sottolineatura nei nomi (o in qualsiasi altro luogo), ma faccio un'eccezione per i nomi delle macro del preprocessore, poiché di solito sono tutti in maiuscolo.


5
Il problema con l'uso di m, piuttosto che m_ (o _) è con l'attuale moda per il caso camel rende difficile leggere alcuni nomi di variabili.
Martin Beckett,

1
@Neil sono con te. @mgb: odio i nomi che iniziano con '_' È solo un invito a far andare le cose in futuro.
Martin York,

1
@Neil: Quale convenzione usi allora, se non usi i trattini bassi e non usi la camelcase?
jalf

2
La mia comprensione era che è camelCase che rende confuso l'utilizzo di m solo per variabili come 'apData' - diventa 'mapData' anziché 'm_apData'. Uso _camelCase per variabili membro protette / private perché si distingue
Martin Beckett

10
@MartinBeckett: dovresti capitalizzare ain quello scenario-- altrimenti non lo farai nel modo giusto. mApData( mprefisso, quindi il nome della variabile è apData).
Platinum Azure,

8

Il motivo principale di un prefisso membro è di distinguere tra una funzione membro locale e una variabile membro con lo stesso nome. Questo è utile se usi getter con il nome della cosa.

Tener conto di:

class person
{
public:
    person(const std::string& full_name)
        : full_name_(full_name)
    {}

    const std::string& full_name() const { return full_name_; }
private:
    std::string full_name_;
};

In questo caso la variabile membro non può essere chiamata full_name. È necessario rinominare la funzione membro in get_full_name () o decorare la variabile membro in qualche modo.


1
Questo è il motivo per cui ho prefisso. Penso che foo.name()sia molto più leggibile che foo.get_name()secondo me.
Terrabits

6

Non penso che una sintassi abbia un valore reale su un'altra. Tutto si riduce, come hai già detto, all'uniformità tra i file di origine.

L'unico punto in cui trovo tali regole interessanti è quando ho bisogno di 2 cose denominate identica, ad esempio:

void myFunc(int index){
  this->index = index;
}

void myFunc(int index){
  m_index = index;
}

Lo uso per differenziare i due. Inoltre, quando eseguo il wrapping delle chiamate, come da Windows Dll, RecvPacket (...) dalla DLL potrebbe essere racchiuso in RecvPacket (...) nel mio codice. In queste occasioni particolari l'uso di un prefisso come "_" potrebbe rendere i due simili, facile identificare quale sia quale, ma diverso per il compilatore


6

Alcune risposte si concentrano sul refactoring, piuttosto che sulle convenzioni di denominazione, come modo per migliorare la leggibilità. Non credo che uno possa sostituire l'altro.

Ho conosciuto programmatori che non si sentono a proprio agio con l'uso delle dichiarazioni locali; preferiscono posizionare tutte le dichiarazioni in cima a un blocco (come in C), quindi sanno dove trovarle. Ho scoperto che, laddove l'ambito lo consente, dichiarare le variabili in cui vengono utilizzate per la prima volta riduce il tempo che passo a guardare indietro per trovare le dichiarazioni. (Questo è vero per me anche per le piccole funzioni.) Ciò mi rende più facile capire il codice che sto guardando.

Spero che sia abbastanza chiaro come ciò si riferisca alle convenzioni di denominazione dei membri: quando i membri hanno un prefisso uniforme, non devo mai guardare indietro; So che la dichiarazione non sarà nemmeno trovata nel file sorgente.

Sono sicuro che non ho iniziato preferendo questi stili. Eppure, nel tempo, lavorando in ambienti in cui sono stati utilizzati in modo coerente, ho ottimizzato il mio pensiero per trarne vantaggio. Penso che sia possibile che molte persone che attualmente si sentono a disagio con loro vengano anche a preferirli, dato un uso coerente.


5

Quelle convenzioni sono proprio questo. La maggior parte dei negozi utilizza convenzioni di codice per facilitare la leggibilità del codice in modo che chiunque possa facilmente guardare un pezzo di codice e decifrare rapidamente tra cose come membri pubblici e privati.


"tra cose come membri pubblici e privati" - quanto è comune? non ricordo di averlo visto, ma di nuovo, non vado in giro a rivedere basi di codice o altro.
underscore_d

Non lo faccio con il mio codice, ma ho lavorato in posti dove dovevamo farlo in base alle loro guide alle convenzioni sul codice. Preferisco non farlo poiché quasi tutti gli IDE mostreranno alle variabili private un colore diverso.
Mr. Will

Hmm, immagino che accada solo in situazioni diverse dalla mia. Normalmente uso classtutti i cui membri sono private/ protected, o POD structtutte le cui variabili sono public(e spesso anche const). Quindi, non ho mai bisogno di chiedermi il livello di accesso di un determinato membro.
underscore_d

5

Altri tentano di imporre l'uso di questo-> membro ogni volta che viene utilizzata una variabile membro

Questo di solito perché non esiste un prefisso . Il compilatore necessita di informazioni sufficienti per risolvere la variabile in questione, sia essa un nome univoco a causa del prefisso o tramite la thisparola chiave.

Quindi sì, penso che i prefissi siano ancora utili. Preferirei digitare '_' per accedere a un membro piuttosto che 'this->'.


3
il compilatore può risolverlo comunque ... le variabili locali nasconderanno quelle di portata maggiore nella maggior parte dei linguaggi. È per il (dubbio) vantaggio degli umani che leggono il codice. Qualsiasi IDE decente metterà in evidenza gente del posto / membri / globali in modi diversi, quindi non c'è bisogno di questo tipo di cose
rmeador

1
Esattamente. I locali nasconderanno i membri della classe. Considera un costruttore che imposta questi membri. Di solito ha senso nominare i parametri come i membri.
Kent Boogaart,

6
Perché è un odore di codice? Direi che è perfettamente comune e ragionevole, soprattutto quando si tratta di costruttori.
Kent Boogaart,

3
Un costruttore dovrebbe (generalmente) impostare i locali nella sua lista di inizializzazione. E lì, i parametri non ombreggiano i nomi dei campi, ma entrambi sono accessibili - quindi puoi scriverestruct Foo { int x; Foo(int x) : x(x) { ... } };
Pavel Minaev

2
Presumo che il problema si presenti quando Foo(int x, bool blee) : x(x) { if (blee) x += bleecount; } // oops, forgot this->preferisci chiamare le variabili del mio membro qualcosa di utile e quindi fornire parametri del costruttore che corrispondono ai loro nomi abbreviati:Foo(int f) : foo(f) {...}
Steve Jessop

4

Altre lingue useranno convenzioni di codifica, tendono solo ad essere diverse. C # per esempio ha probabilmente due stili diversi che le persone tendono ad usare, uno dei metodi C ++ (_variable, mVariable o altri prefissi come la notazione ungherese), o quello che io chiamo metodo StyleCop.

private int privateMember;
public int PublicMember;

public int Function(int parameter)
{
  // StyleCop enforces using this. for class members.
  this.privateMember = parameter;
}

Alla fine, diventa ciò che la gente sa e ciò che sembra migliore. Personalmente penso che il codice sia più leggibile senza notazione ungherese, ma può essere più facile trovare una variabile con intellisense, ad esempio se la notazione ungherese è allegata.

Nel mio esempio sopra, non hai bisogno di un prefisso m per le variabili membro perché prefisso il tuo utilizzo con questo. indica la stessa cosa in un metodo forzato dal compilatore.

Questo non significa necessariamente che gli altri metodi siano cattivi, le persone si attengono a ciò che funziona.


3

Quando hai un metodo grande o blocchi di codice, è conveniente sapere immediatamente se usi una variabile locale o un membro. è per evitare errori e per una migliore chiarezza!


3
Se hai un metodo grande, per una migliore chiarezza scomponilo.
sabato

4
Ci sono molte ragioni per non scomporre alcuni grandi metodi. Ad esempio, se il tuo metodo deve mantenere un sacco di stato locale, devi passare molti parametri nei tuoi metodi subordinati, creare nuove classi che esistono esclusivamente allo scopo di passare i dati tra questi metodi o dichiarare i dati di stato come dati membro della classe genitore. Tutti questi hanno problemi che potrebbero influire sulla chiarezza o sulla manutenibilità del metodo, rispetto a un singolo metodo long-ish (in particolare uno la cui logica è semplice).
Steve Broberg,

3
@sbi: le linee guida sono proprio questo; linee guida, non regole. A volte sono necessari metodi di grandi dimensioni che non si prestano logicamente a essere divisi, a volte i nomi dei parametri si scontrano con i membri.
Ed S.

Per favore, non rendere pubbliche le variabili del tuo membro. Usa solo gli accessori. Le parentesi dovrebbero dire al lettore che si tratta di una variabile membro.
jkeys,

Nota che c'è un avvertimento in gcc (> = 4.6) per rilevare lo scontro di nomi:-Wshadow
Caduchon,

3

IMO, questo è personale. Non sto inserendo alcun prefisso. Ad ogni modo, se si intende che il codice sia pubblico, penso che dovrebbe avere dei prefissi, quindi può essere più leggibile.

Spesso le grandi aziende utilizzano le proprie cosiddette "regole per gli sviluppatori".
A proposito, il più divertente ma più intelligente che ho visto è stato DRY KISS (non ripetere te stesso. Keep It Simple, Stupid). :-)


3

Come altri hanno già detto, l'importanza è quella di essere colloquiali (adattare gli stili di denominazione e le convenzioni alla base di codice in cui si sta scrivendo) e di essere coerenti.

Per anni ho lavorato su una base di codice di grandi dimensioni che utilizza sia la convenzione "this->", sia un postfix notazione di sottolineatura per le variabili membro. Nel corso degli anni ho anche lavorato su progetti più piccoli, alcuni dei quali non avevano alcun tipo di convenzione per la denominazione delle variabili dei membri, e altri che avevano convenzioni diverse per la denominazione delle variabili dei membri. Di quei progetti più piccoli, ho sempre trovato quelli che non avevano alcuna convenzione per essere i più difficili da affrontare rapidamente e capire.

Sono molto ansioso di nominare. Mi agonizzerò per il nome da attribuire a una classe o a una variabile al punto che, se non riesco a trovare qualcosa che ritengo "buono", sceglierò di chiamarlo qualcosa di privo di senso e fornirò un commento descrivendo ciò che realmente è. In questo modo, almeno il nome significa esattamente ciò che intendo che significhi: niente di più e niente di meno. E spesso, dopo averlo usato per un po ', ho scoperto quello che il nome dovrebbe davvero essere e può tornare indietro e modificare o refactoring in modo appropriato.

Un ultimo punto sull'argomento di un IDE che fa il lavoro - è tutto bello e buono, ma gli IDE spesso non sono disponibili negli ambienti in cui ho svolto il lavoro più urgente. A volte l'unica cosa disponibile a quel punto è una copia di 'vi'. Inoltre, ho visto molti casi in cui il completamento del codice IDE ha propagato stupidità come ortografia errata nei nomi. Pertanto, preferisco non dover fare affidamento su una stampella IDE.


3

L'idea originale per i prefissi sulle variabili membro C ++ era di memorizzare informazioni aggiuntive sul tipo che il compilatore non conosceva. Quindi, ad esempio, potresti avere una stringa che è una lunghezza fissa di caratteri e un'altra che è variabile e terminata da uno '\ 0'. Per il compilatore sono entrambi char *, ma se provi a copiare l'uno dall'altro potresti avere grossi problemi. Quindi, dalla cima della mia testa,

char *aszFred = "Hi I'm a null-terminated string";
char *arrWilma = {'O', 'o', 'p', 's'};

dove "asz" indica che questa variabile è "stringa ASCII (terminata con zero) e" arr "indica che questa variabile è un array di caratteri.

Quindi accade la magia. Il compilatore sarà perfettamente soddisfatto di questa affermazione:

strcpy(arrWilma, aszFred);

Ma tu, come essere umano, puoi guardarlo e dire "hey, quelle variabili non sono proprio dello stesso tipo, non posso farlo".

Sfortunatamente molti posti usano standard come "m_" per le variabili membro, "i" per numeri interi, non importa quanto usato, "cp" per i puntatori char. In altre parole, stanno duplicando ciò che il compilatore sa e rendono il codice difficile da leggere allo stesso tempo. Credo che questa pratica perniciosa dovrebbe essere messa al bando dallo statuto e soggetta a severe sanzioni.

Infine, ci sono due punti che dovrei menzionare:

  • L'uso giudizioso delle funzionalità C ++ consente al compilatore di conoscere le informazioni che dovevi codificare in variabili di tipo C non elaborate. Puoi creare classi che consentiranno solo operazioni valide. Questo dovrebbe essere fatto tanto quanto pratico.
  • Se i blocchi di codice sono così lunghi che si dimentica che tipo di una variabile è prima di utilizzarlo, sono modo troppo lunghi. Non usare nomi, riorganizza.

Anche i prefissi che indicano il tipo o il tipo di variabile meritano una discussione, ma mi riferivo principalmente ai prefissi che indicano se qualcosa è un membro / campo (privato). La notazione ungherese inversa che stai citando può essere molto utile se applicata in modo intelligente (come nel tuo esempio). Il mio esempio preferito in cui ha senso sono le coordinate relative e assolute. quando vedi absX = relX puoi vedere chiaramente che qualcosa potrebbe non andare. Puoi anche nominare le funzioni di conseguenza: absX = absFromRel (relX, offset);
VoidPointer

Nota: l'inizializzazione di aszFred è discutibile (offrendo un accesso non const a una stringa letterale) e l'inizializzazione di arrWilma non verrà nemmeno compilata. (Probabilmente intendevi dichiarare arrWilma come un array, anziché come un puntatore!) Nessun problema, dato che hai scritto che è appena fuori dalla testa ... :-)
Niels Dekker il

Oops, hai assolutamente ragione. Ragazzi, non provatelo a casa. Fai questo: 'const char * aszFred = "Ciao, sono una stringa con terminazione nulla"; char arrWilma [] = {'O', 'o', 'p', 's'}; '
AL Flanagan,

3

Il nostro progetto ha sempre usato "suo" come prefisso per i dati dei membri e "il" come prefisso per i parametri, senza prefisso per i locali. È un po 'carino, ma è stato adottato dai primi sviluppatori del nostro sistema perché lo vedevano usato come una convenzione da alcune librerie di sorgenti commerciali che stavamo usando in quel momento (o XVT o RogueWave - forse entrambi). Quindi otterrai qualcosa del genere:

void
MyClass::SetName(const RWCString &theName)
{
   itsName = theName;
}

La grande ragione che vedo per i prefissi di ambito (e nessun altro - odio la notazione ungherese) è che ti impedisce di metterti nei guai scrivendo codice in cui pensi che ti riferisci a una variabile, ma in realtà ti riferisci a un'altra variabile con lo stesso nome definito nell'ambito locale. Evita anche il problema di trovare nomi di variabili per rappresentare lo stesso concetto, ma con ambiti diversi, come nell'esempio sopra. In tal caso, dovresti trovare comunque un prefisso o un nome diverso per il parametro "theName" - perché non creare una regola coerente che si applica ovunque.

Basta usare questo-> non è abbastanza buono - non siamo così interessati a ridurre l'ambiguità come lo siamo nella riduzione degli errori di codifica, e mascherare i nomi con identificatori con ambito locale può essere una seccatura. Certo, alcuni compilatori potrebbero avere la possibilità di generare avvisi per i casi in cui hai mascherato il nome in un ambito più ampio, ma tali avvisi potrebbero diventare un fastidio se stai lavorando con un ampio set di librerie di terze parti che hanno scelto nomi per variabili non utilizzate che occasionalmente si scontrano con le tue.

Per quanto riguarda il suo / lo stesso - trovo onestamente più facile da scrivere rispetto ai caratteri di sottolineatura (come tipografo touch, evito i caratteri di sottolineatura ogni volta che è possibile - troppo allungamento dalle righe di casa) e lo trovo più leggibile di un carattere di sottolineatura misterioso.


Questa è la soluzione più intuitiva con la curva di apprendimento più veloce che abbia mai sentito. Vorrei che le lingue parlate fossero più flessibili per gestire tutte quelle in modo da non dover pensare a nuove tecniche per risolvere le ambiguità nel codice.
Guney Ozsan,

2

Lo uso perché Intellisense di VC ++ non è in grado di dire quando mostrare i membri privati ​​quando si accede fuori dalla classe. L'unica indicazione è un piccolo simbolo di "blocco" sull'icona del campo nell'elenco Intellisense. Semplifica l'identificazione dei membri privati ​​(campi). Anche un'abitudine da C # a dire il vero.

class Person {
   std::string m_Name;
public:
   std::string Name() { return m_Name; }
   void SetName(std::string name) { m_Name = name; }
};

int main() {
  Person *p = new Person();
  p->Name(); // valid
  p->m_Name; // invalid, compiler throws error. but intellisense doesn't know this..
  return 1;
}

2

Penso che, se hai bisogno di prefissi per distinguere i membri della classe dai parametri della funzione membro e dalle variabili locali, o la funzione è troppo grande o le variabili hanno un nome errato. Se non si adatta allo schermo in modo da poter vedere facilmente cosa è cosa, refactor.

Dato che spesso sono dichiarati lontani da dove vengono utilizzati, trovo che le convenzioni di denominazione per le costanti globali (e le variabili globali, sebbene l'IMO non abbia mai bisogno di usarle) hanno senso. Ma per il resto, non vedo molto bisogno.

Detto questo, ho usato un carattere di sottolineatura alla fine di tutti i membri della classe privata. Poiché tutti i miei dati sono privati, ciò implica che i membri hanno un carattere di sottolineatura finale. Di solito non lo faccio più nelle nuove basi di codice, ma poiché, come programmatore, lavori principalmente con il vecchio codice, lo faccio ancora molto. Non sono sicuro che la mia tolleranza per questa abitudine derivi dal fatto che lo facevo sempre e lo faccio ancora regolarmente o se ha davvero più senso della marcatura delle variabili membro.


2
Ciò riflette molto la mia opinione su questo problema. Il codice dovrebbe essere leggibile senza ricorrere ai prefissi. Forse non vediamo così tanti usi dei prefissi in linguaggi più moderni perché le loro comunità di utenti hanno abbracciato la leggibilità un po 'più di quello che a volte vedi in C ++. Naturalmente, C ++ può e dovrebbe essere leggibile. È solo che un sacco di C ++ illeggibile è stato scritto nel corso degli anni.
VoidPointer


1

È utile distinguere tra variabili membro e variabili locali a causa della gestione della memoria. In generale, le variabili membro allocate in heap dovrebbero essere distrutte nel distruttore, mentre le variabili locali allocate in heap dovrebbero essere distrutte all'interno di tale ambito. L'applicazione di una convenzione di denominazione alle variabili membro facilita la corretta gestione della memoria.


come mai? Il distruttore non ha accesso alle variabili locali dichiarate in altre funzioni, quindi non c'è spazio per la confusione lì. Inoltre, non dovrebbero esistere variabili locali allocate in heap . E le variabili membro allocate in heap dovrebbero esistere solo all'interno delle classi RAII, praticamente.
jalf

"Le variabili locali allocate in heap non dovrebbero esistere" è un po 'forte. Ma se / quando li usi, è estremamente importante assicurarsi che vengano deallocati correttamente, quindi una convenzione di denominazione disciplinata per le variabili membro rispetto a quella locale aiuta incommensurabilmente a garantirlo.
Frank

1

Code Complete consiglia m_varname per le variabili membro.

Anche se non ho mai pensato che la notazione m_ fosse utile, darei peso all'opinione di McConnell nella costruzione di uno standard.


2
A meno che non spieghi perché il trattino basso. Sono un grande fan del suo libro "Rapid Development", che ho raccomandato qui numerose volte, ma molto meno di "Code Complete" (che ammetto di non aver letto da quando è uscito per la prima volta).

1

Non uso quasi mai i prefissi davanti ai nomi delle mie variabili. Se stai usando un IDE abbastanza decente dovresti essere in grado di refactoring e trovare facilmente i riferimenti. Uso nomi molto chiari e non ho paura di avere nomi con variabili lunghe. Non ho mai avuto problemi con l'ambito neanche con questa filosofia.

L'unica volta che utilizzo un prefisso sarebbe sulla riga della firma. Prefisso i parametri a un metodo con _ in modo da poter programmare in modo difensivo attorno a loro.


1

Non dovresti mai aver bisogno di un tale prefisso. Se un tale prefisso ti offre qualche vantaggio, il tuo stile di codifica in generale deve essere riparato, e non è il prefisso che impedisce al tuo codice di essere chiaro. I nomi di variabili non valide tipici includono "altro" o "2". Non lo risolvi richiedendo che sia Altro, lo risolvi facendo in modo che lo sviluppatore pensi a cosa sta facendo quella variabile nel contesto di quella funzione. Forse intendeva remoteSide, o newValue o secondTestListener o qualcosa del genere.

È un anacronismo efficace che si è ancora propagato troppo lontano. Smetti di aggiungere il prefisso alle tue variabili e dai loro i nomi propri la cui chiarezza riflette per quanto tempo vengono usate. Fino a 5 linee potresti chiamarlo "i" senza confusione; oltre 50 righe hai bisogno di un nome piuttosto lungo.


1

Mi piacciono i nomi delle variabili per dare un significato solo ai valori che contengono e lasciare il modo in cui vengono dichiarati / implementati fuori dal nome. Voglio sapere cosa significa il valore, punto. Forse ho fatto più di una quantità media di refactoring, ma trovo che incorporare il modo in cui qualcosa è implementato nel nome rende il refactoring più noioso di quanto debba essere. I prefissi che indicano dove o come vengono dichiarati i membri dell'oggetto sono specifici dell'implementazione.

color = Red;

Il più delle volte, non mi interessa se il rosso è un enum, una struct o altro, e se la funzione è così grande che non riesco a ricordare se il colore è stato dichiarato localmente o è un membro, è probabilmente il momento di rompere la funzione in unità logiche più piccole.

Se la tua complessità ciclomatica è così grande che non puoi tenere traccia di ciò che sta accadendo nel codice senza indizi specifici dell'implementazione incorporati nei nomi delle cose, molto probabilmente dovrai ridurre la complessità della tua funzione / metodo.

Principalmente, uso questo "solo" nei costruttori e negli inizializzatori.


0

Uso m_ per le variabili membro solo per sfruttare Intellisense e la relativa funzionalità IDE. Quando sto codificando l'implementazione di una classe, posso digitare m_ e vedere la casella combinata con tutti i membri m_ raggruppati insieme.

Ma potrei vivere senza m_ senza problemi, ovviamente. È solo il mio stile di lavoro.


Puoi anche digitarethis->
Toast,

0

Secondo le NORME DI CODIFICA C ++ DEL VEICOLO AEREO COMUNE DA STRIKE (dicembre 2005):

Regola AV 67

I dati pubblici e protetti dovrebbero essere usati solo nelle strutture, non nelle classi. Motivazione: Una classe è in grado di mantenere il proprio invariante controllando l'accesso ai suoi dati. Tuttavia, una classe non può controllare l'accesso ai suoi membri se tali membri non sono privati. Quindi tutti i dati in una classe dovrebbero essere privati.

Pertanto, il prefisso "m" diventa inutile poiché tutti i dati devono essere privati.

Ma è una buona abitudine usare il prefisso p prima di un puntatore poiché è una variabile pericolosa.


0

Molte di queste convenzioni sono di un tempo senza redattori sofisticati. Consiglierei di usare un IDE corretto che ti permetta di colorare ogni tipo di variabile. Il colore è di gran lunga più facile da individuare rispetto a qualsiasi prefisso.

Se hai bisogno di ottenere ancora più dettagli su una variabile, qualsiasi IDE moderno dovrebbe essere in grado di mostrartelo spostando il cursore o il cursore su di essa. E se si utilizza una variabile in modo errato (ad esempio un puntatore con l'operatore.) Si otterrà comunque un errore.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.