Soluzione elegante per duplicare, const e non-const, getter?

Non lo odi quando lo hai

class Foobar { public: Something& getSomething(int index) { // big, non-trivial chunk of code... return something; } const Something& getSomething(int index) const { // big, non-trivial chunk of code... return something; } } 

Non possiamo implementare nessuno di questi metodi con l’altro, perché non è ansible chiamare la versione non const dalla versione const (errore del compilatore). Sarà richiesto un cast per chiamare la versione const da quella non const .

C’è una soluzione davvero elegante a questo, se no, qual è il più vicino a uno?

Ricordo da uno dei libri Effective C ++ che il modo per farlo è implementare la versione non-const eliminando il const dall’altra funzione.

Non è particolarmente carino, ma è sicuro. Poiché la funzione membro che la chiama è non-const, l’object stesso è non-const, e il cast via è const.

 class Foo { public: const int& get() const { //non-trivial work return foo; } int& get() { return const_cast(static_cast(this)->get()); } }; 

Che ne dite di:

 template OUT BigChunk(IN self, int index) { // big, non-trivial chunk of code... return something; } struct FooBar { Something &getSomething(int index) { return BigChunk(this,index); } const Something &getSomething(int index) const { return BigChunk(this,index); } }; 

Ovviamente avrai ancora la duplicazione del codice object, ma non la duplicazione del codice sorgente. A differenza dell’approccio const_cast, il compilatore controllerà la correttezza delle costanti per entrambe le versioni del metodo.

Probabilmente hai bisogno di dichiarare le due istanze interessanti di BigChunk come amici della class. Questo è un buon uso dell’amico, dal momento che le funzioni dell’amico sono nascoste vicino all’amico, quindi non c’è il rischio di un accoppiamento non vincolato (ooh-er!). Ma non tenterò la syntax per farlo ora. Sentiti libero di aggiungere.

È probabile che BigChunk abbia bisogno di deferenza per se stesso, nel qual caso il suddetto ordine di definizione non funzionerà molto bene, e alcune dichiarazioni anticipate saranno necessarie per risolverlo.

Inoltre, al fine di evitare qualche intorpidimento nel trovare BigChunk nell’header e decidere di istanziarlo e chiamarlo anche se è moralmente privato, puoi spostare l’intero lotto nel file cpp per FooBar. In uno spazio dei nomi anonimo. Con collegamento interno. E un cartello che dice “attenti al leopardo”.

Vorrei trasmettere il const al non const (seconda opzione).

Cerca di eliminare i getter effettuando il refactoring del codice. Usa le funzioni o le classi di amici se solo un numero molto piccolo di altre cose ha bisogno di Qualcosa.

In generale, Getters e Setter interrompono l’incapsulamento perché i dati sono esposti al mondo. L’uso di amici espone solo i dati a pochi eletti, quindi offre un migliore incapsulamento.

Naturalmente, questo non è sempre ansible, quindi potresti essere bloccato con i getter. Per lo meno, la maggior parte o tutta la “parte non banale del codice” dovrebbe essere in una o più funzioni private, chiamate da entrambi i getter.

Perché non estrarre semplicemente il codice comune in una funzione privata separata, e poi chiamarne gli altri due?

Il riferimento const all’object ha senso (stai mettendo una restrizione sull’accesso di sola lettura a quell’object), ma se devi consentire un riferimento non const , potresti anche rendere pubblico il membro.

Credo che questo sia un la Scott Meyers (Efficient C ++).

Il concetto di “const” è lì per una ragione. Per me stabilisce un contratto molto importante in base al quale vengono scritte ulteriori istruzioni di un programma. Ma puoi fare qualcosa sulle seguenti linee: –

  1. rendi il tuo membro ‘mutevole’
  2. rendere il const ‘getter’
  3. restituire riferimento non const

Con questo, si può usare un riferimento const sul LHS se è necessario mantenere la funzionalità const in cui si utilizza il getter con l’uso non const (pericoloso). Ma ora è compito del programmatore mantenere gli invarianti di class.

Come è stato detto in SO, la costanza di un object const originariamente definito e il suo utilizzo è un UB, quindi non utilizzerei i cast. Anche la creazione di un const object non const e poi di nuovo la costanza di lancio non apparirebbe troppo buona.

Un’altra linea guida di codifica che ho visto utilizzato in alcuni team è: –

  • Se una variabile membro deve essere modificata all’esterno della class, restituire sempre un puntatore tramite una funzione membro non const.
  • Nessuna funzione membro può restituire riferimenti non const. Solo i riferimenti const sono consentiti dalle funzioni membro const.

Ciò consente una certa coerenza nella base di codice globale e il chiamante può vedere chiaramente quali chiamate possono modificare la variabile membro.

Oserei suggerire di usare il preprocessore:

 #define ConstFunc(type_and_name, params, body) \ const type_and_name params const body \ type_and_name params body class Something { }; class Foobar { private: Something something; public: #define getSomethingParams \ ( \ int index \ ) #define getSomethingBody \ { \ return something; \ } ConstFunc(Something & getSomething, getSomethingParams, getSomethingBody) };