Domanda

Nel libro (altrimenti) eccellente Standard di codifica C ++ , Articolo 44, intitolato " Preferisce scrivere funzioni non amichevoli non membro " , Sutter e Alexandrescu raccomandano che solo le funzioni che necessitano realmente dell'accesso ai membri di una classe siano esse stesse membri di quella classe. Tutte le altre operazioni che possono essere scritte utilizzando solo le funzioni membro non devono far parte della classe. Dovrebbero essere non membri e non amici. Gli argomenti sono questi:

  • Promuove l'incapsulamento, perché c'è meno codice che deve accedere agli interni di una classe.
  • Semplifica la scrittura di modelli di funzioni, poiché non è necessario indovinare ogni volta se una funzione è membro o meno.
  • Mantiene piccola la classe, il che a sua volta semplifica il test e la manutenzione.

Anche se vedo il valore in questi argomenti, vedo un grosso svantaggio: il mio IDE non può aiutarmi a trovare queste funzioni! Ogni volta che ho un oggetto di qualche tipo e voglio vedere quali operazioni sono disponibili su di esso, non posso semplicemente digitare " pMysteriousObject- > " e ottenere un elenco delle funzioni membro.

Mantenere un design pulito significa in definitiva semplificare la vita della programmazione. Ma questo renderebbe il mio molto più difficile.

Quindi mi chiedo se valga davvero la pena. Come lo gestisci?

È stato utile?

Soluzione

Non dovrò essere d'accordo con Sutter e Alexandrescu su questo. Penso che se il comportamento della funzione foo () rientra nell'ambito delle responsabilità della classe Bar , allora foo () dovrebbe far parte di bar () .

Il fatto che foo () non abbia bisogno dell'accesso diretto ai dati dei membri di Bar non significa che non sia concettualmente parte della barra . Può anche significare che il codice è ben ponderato. Non è raro avere funzioni membro che svolgono tutto il loro comportamento tramite altre funzioni membro e non vedo perché dovrebbe essere.

Sono pienamente d'accordo sul fatto che le funzioni relative alle periferie dovrebbero non far parte della classe, ma se qualcosa è fondamentale per le responsabilità della classe, non c'è motivo per cui non dovrebbe essere un membro, indipendentemente dal fatto che si confonde direttamente con i dati dei membri.

Per quanto riguarda questi punti specifici:

  

Promuove l'incapsulamento, perché c'è meno codice che richiede l'accesso agli interni di una classe.

In effetti, meno funzioni accedono direttamente agli interni, meglio è. Ciò significa che avere funzioni membro il più possibile via altre funzioni membro è una buona cosa. Dividere le funzioni ben ponderate fuori dalla classe ti lascia solo con una mezza classe, che richiede un mucchio di funzioni esterne per essere utile. Anche allontanare le funzioni ben ponderate dalle loro classi sembra scoraggiare la scrittura di funzioni ben fatturate.

  

Semplifica la scrittura di modelli di funzioni, poiché non è necessario indovinare ogni volta se una funzione è membro o meno.

Non lo capisco affatto. Se estrai un sacco di funzioni dalle classi, avrai una maggiore responsabilità sui modelli di funzione. Sono costretti a supporre che la funzionalità anche meno sia fornita dagli argomenti del modello di classe, a meno che non supponiamo che la maggior parte delle funzioni estratte dalle loro classi venga convertita in un modello (ugh).

  

Mantiene la classe piccola, il che a sua volta semplifica il test e la manutenzione.

Uhm, certo. Inoltre, crea molte funzioni esterne aggiuntive da testare e mantenere. Non riesco a vedere il valore in questo.

Altri suggerimenti

Scott Meyers ha un'opinione simile a Sutter, vedi qui .

Indica chiaramente anche quanto segue:

" Basandosi sul suo lavoro con varie classi simili a stringhe, Jack Reeves ha osservato che alcune funzioni semplicemente non si sentono " quando sono diventati non membri, anche se potrebbero essere non membri non amici. Il "migliore" l'interfaccia per una classe può essere trovata solo bilanciando molte preoccupazioni concorrenti, di cui il grado di incapsulamento è solo uno. "

Se una funzione fosse qualcosa che "ha senso" per essere una funzione membro, rendila una. Allo stesso modo, se in realtà non fa parte dell'interfaccia principale e "ha senso". per essere un non-membro, fallo.

Una nota è che con le versioni sovraccaricate di esempio operator == (), la sintassi rimane invariata. Quindi in questo caso non hai motivo non per renderlo una funzione mobile non amico non membro dichiarata nello stesso posto della classe, a meno che non abbia davvero bisogno dell'accesso ai membri privati ??(nella mia esperienza raramente lo farà). E anche allora puoi definire operator! = () Un non membro e in termini di operator == ().

Non penso che sarebbe sbagliato dire che tra loro, Sutter, Alexandrescu e Meyers hanno fatto di più per la qualità del C ++ di chiunque altro.

Una semplice domanda che pongono è:

  

Se una funzione di utilità ha due classi indipendenti come parametri, quale classe dovrebbe "possedere" la funzione membro?

Un altro problema è che puoi aggiungere funzioni membro solo quando la classe in questione è sotto il tuo controllo. Qualsiasi funzione di supporto che scrivi per std :: string dovrà essere non membro poiché non puoi riaprire la definizione della classe.

Per entrambi questi esempi, il tuo IDE fornirà informazioni incomplete e dovrai utilizzare il "vecchio modo" " ;.

Dato che gli esperti C ++ più influenti al mondo ritengono che le funzioni non membro con un parametro di classe facciano parte dell'interfaccia delle classi, questo è più un problema con il tuo IDE piuttosto che lo stile di codifica.

Probabilmente il tuo IDE cambierà in una o due versioni e potresti persino riuscire a farle aggiungere questa funzione. Se modifichi il tuo stile di codifica per adattarlo all'IDE di oggi, potresti scoprire di avere maggiori problemi in futuro con un codice non estendibile / non mantenibile.

È vero che le funzioni esterne non dovrebbero far parte dell'interfaccia. In teoria, la tua classe dovrebbe contenere solo i dati ed esporre l'interfaccia per ciò che è previsto e non per le funzioni utilitaristiche. L'aggiunta di funzioni di utilità all'interfaccia non fa che aumentare la base di codici di classe e renderla meno gestibile. Attualmente mantengo una classe con circa 50 metodi pubblici, che è semplicemente pazzo.

Ora, in realtà, concordo sul fatto che non è facile da applicare. Spesso è più semplice aggiungere un altro metodo alla tua classe, ancora di più se stai usando un IDE che può davvero semplicemente aggiungere un nuovo metodo a una classe esistente.

Al fine di mantenere semplici le mie classi ed essere ancora in grado di centralizzare le funzioni esterne, utilizzo spesso una classe di utilità che funziona con la mia classe, o anche con spazi dei nomi. Comincio creando la classe che avvolgerà i miei dati ed esporrà l'interfaccia più semplice possibile. Quindi creo una nuova classe per ogni compito che ho a che fare con la classe.

Esempio: crea una classe Point, quindi aggiungi una classe PointDrawer per disegnarla su una bitmap, PointSerializer per salvarla, ecc.

Se dai loro un prefisso comune, forse il tuo IDE ti aiuterà se digiti

::prefix

o

namespace::prefix

In molte lingue OOP i metodi non di classe non amici sono cittadini di terza classe che risiedono in un orfanotrofio non collegato a nulla. Quando scrivo un metodo, mi piace scegliere buoni genitori - una classe adatta - dove hanno le migliori possibilità di sentirsi i benvenuti e aiutare.

Avrei pensato che l'IDE ti stesse davvero aiutando.

L ' IDE nasconde le funzioni protette dall'elenco perché non sono disponibili al pubblico proprio come previsto dal progettista della classe .

Se fossi stato nell'ambito della classe e avessi digitato this-> , le funzioni protette sarebbero visualizzate nell'elenco.

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