Domanda

Ho un semplice contenitore di classe con un costruttore di copia.

Si consiglia di utilizzare metodi getter e setter, o l'accesso a variabili membro direttamente?

public Container 
{
   public:
   Container() {}

   Container(const Container& cont)          //option 1
   { 
       SetMyString(cont.GetMyString());
   }

   //OR

   Container(const Container& cont)          //option 2
   {
      m_str1 = cont.m_str1;
   }

   public string GetMyString() { return m_str1;}       

   public void SetMyString(string str) { m_str1 = str;}

   private:

   string m_str1;
}
  • Nell'esempio, il codice è in linea, ma nel nostro codice vero e proprio non c'è il codice in linea.

Aggiornamento (29 Sett 09):

Alcune di queste risposte sono scritte bene, tuttavia, sembrano mancanti il punto della domanda:

  • questo è un semplice esempio artificioso per discutere utilizzando metodi getter/setter vs variabili

  • liste di inizializzazione o privato validatore funzioni non sono realmente parte di questa domanda.Mi chiedo se sia il design di rendere il codice più facile da mantenere ed espandere.

  • Alcuni ppl si stanno concentrando sulla stringa in questo esempio, tuttavia, è solo un esempio, immaginate che sia un oggetto diverso, invece.

  • Io non sono preoccupato per le prestazioni.non siamo di programmazione per il PDP-11

È stato utile?

Soluzione

Si prevedono come la stringa restituita, ad esempio.lo spazio bianco tagliato, null controllato, etc.?Stesso con SetMyString(), se la risposta è sì, si sta meglio con i metodi di accesso in quanto non è necessario modificare il codice in mille posti, ma solo modificare quelli i metodi getter e setter.

Altri suggerimenti

EDIT: Rispondere la modifica in questione :)

questo è un semplice esempio artificioso a discutere utilizzando metodi getter/setter vs variabili

Se si dispone di un semplice insieme di variabili, che non hanno bisogno di qualsiasi tipo di convalida, né di elaborazione aggiuntivi, allora si potrebbe considerare l'utilizzo di un CONTENITORE, invece.Da Stroustrup FAQ:

Un ben progettato classe presenta una pulita interfaccia semplice e per i suoi utenti, nascondendo la sua rappresentazione e risparmio i suoi utenti da conoscere su che la rappresentazione.Se il la rappresentazione non deve essere nascosto dire, perché gli utenti dovrebbero essere in grado di cambiare i dati membro in qualsiasi modo come si può pensare che la classe come "solo una semplice struttura di dati"

In breve, questo non è JAVA.non si deve scrivere getter/setter, perché sono così male come esporre le variabili di loro stessi.

liste di inizializzazione o privato validatore funzioni non sono davvero parte di questa domanda.Mi chiedo se il design di rendere il codice più facile da mantenere ed espandere.

Se si esegue la copia di un altro oggetto di variabili, quindi l'oggetto di origine dovrebbe essere in uno stato valido.Come ha fatto il malato ha formato oggetto di origine venne costruita, in primo luogo?!Non dovrebbe costruttori a fare il lavoro di validazione?non modificare le funzioni membro responsabile di mantenere l'invariante di classe con la convalida dell'input?Perché si convalida un "valido" oggetto in un costruttore di copia?

Io non sono preoccupato per le prestazioni.non siamo di programmazione per il PDP-11

Questo è lo stile più elegante, anche se in C++ il più elegante, il codice ha le migliori caratteristiche di prestazioni in genere.


Si consiglia di utilizzare un initializer list.Nel codice, m_str1 è default costruito quindi assegnato un nuovo valore.Il codice potrebbe essere qualcosa di simile a questo:

class Container 
{
public:
   Container() {}

   Container(const Container& cont) : m_str1(cont.m_str1)
   { }

   string GetMyString() { return m_str1;}       
   void SetMyString(string str) { m_str1 = str;}
private:
   string m_str1;
};

@cbrulak non Si deve convalidare IMO cont.m_str1 nel copy constructor.Quello che voglio fare, è quello di convalidare le cose in constructors.Convalida in copy constructor significa che si sta copiando un malato formato oggetto, in primo luogo, per esempio:

Container(const string& str) : m_str1(str)
{
    if(!valid(m_str1)) // valid() is a function to check your input
    {
        // throw an exception!
    }
}

Si dovrebbe usare una lista di inizializzazione, e allora la questione diventa priva di significato, come in:

Container(const Container& rhs)
  : m_str1(rhs.m_str1)
{}

C'è una grande sezione di Matthew Wilson Imperfetto C++ che spiega tutto sul Membro Liste di Inizializzazione, e su come si possono usare in combinazione con const e/o riferimenti a rendere il codice più sicuro.

Modifica:un esempio che mostra la convalida e la const:

class Container
{
public:
  Container(const string& str)
    : m_str1(validate_string(str))
  {}
private:
  static const string& validate_string(const string& str)
  {
    if(str.empty())
    {
      throw runtime_error("invalid argument");
    }
    return str;
  }
private:
  const string m_str1;
};

Come è scritto adesso (con nessun titolo di ingresso o di uscita) il getter e setter (funzione di accesso e mutator, se si preferisce) stanno realizzando assolutamente nulla, quindi si potrebbe anche solo fare la stringa di pubblico e da fare con esso.

Se il codice vero e proprio davvero qualificare la stringa, quindi le probabilità sono piuttosto buone che ciò che si sta trattando non è propriamente una stringa di tutto, invece, è solo qualcosa che assomiglia molto a una stringa.Che cosa si sta facendo in questo caso è abusato il tipo di sistema, una sorta di esposizione di una stringa, quando il tipo reale è solo qualcosa di un po ' come una stringa.Si sta fornendo il setter per cercare di imporre restrizioni che il tipo reale rispetto ad una vera e propria collana.

Quando la si guarda da quella direzione, la risposta diventa abbastanza evidente:piuttosto che una stringa, con un setter per rendere la stringa di agire come alcuni altri (più ristretto) tipo, che cosa si dovrebbe fare invece è la definizione di una classe reale per il tipo che si vuole veramente.Una volta definita la classe correttamente, è creare un'istanza del pubblico.Se (come sembra essere il caso qui) è ragionevole assegnare un valore che inizia come una stringa, quindi, che la classe deve contenere un operatore di assegnamento, che prende una stringa come argomento.Se (come sembra essere il caso qui) è ragionevole per la conversione che tipo di una stringa in alcune circostanze, può anche includere l'operatore di cast che produce una stringa come risultato.

Questo dà un reale miglioramento rispetto all'uso di un setter e getter, in un ambiente di classe.In primo luogo, quando hai messo quelli che in un ambiente di classe, è facile per il codice all'interno della classe, per bypassare il getter/setter, perdendo l'esecuzione di qualsiasi setter che doveva rispettare.Secondo, mantiene un aspetto normale notazione.Utilizzando un getter e setter ti obbliga a scrivere un codice che semplicemente brutto e difficile da leggere.

Uno dei maggiori punti di forza di una stringa di classe in C++ utilizza l'overload dell'operatore in modo che si può sostituire qualcosa come:

strcpy(strcat(filename, ".ext"));

con:

filename += ".ext";

per migliorare la leggibilità.Ma guardate cosa succede se la stringa è parte di una classe che ci costringe a passare attraverso un setter e getter:

some_object.setfilename(some_object.getfilename()+".ext");

Se non altro, il codice C è in realtà più leggibile di questo pasticcio.D'altra parte, considerare che cosa succede se fare il lavoro giusto, con un pubblico oggetto di una classe che definisce un operatore stringa e operatore=:

some_object.filename += ".ext";

Bella, semplice e leggibile, proprio come dovrebbe essere.Meglio ancora, se abbiamo bisogno di imporre qualcosa circa la stringa, possiamo controllare solo quella piccola classe, abbiamo davvero solo guardare uno o due specifici, luoghi conosciuti (operatore=, eventualmente, un costruttore o due per classe) per capire che è sempre applicata -- una storia completamente diversa rispetto a quando si utilizza un setter di provare a fare il lavoro.

Chiedetevi che cosa sono i costi e i benefici.

Costo:maggiore overhead di runtime.La chiamata di funzioni virtuali in ctors è una cattiva idea, ma setter e getter è improbabile che essere virtuale.

Vantaggi:se il setter/getter fa qualcosa di complicato, non sei la ripetizione del codice;se fa qualcosa di istintivo, non stai dimenticando di farlo.

Il rapporto costi/benefici variano per le diverse classi.Una volta che hai accertato che il rapporto, utilizzare il vostro giudizio.Per immutabile classi, ovviamente, non sono i setter, e non avete bisogno di getter (come const membri e i riferimenti possono essere pubblico, come nessuno può cambiare/riposizionare loro).

Non c'è pallottola d'argento come come scrivere il costruttore di copia.Se la classe ha solo i membri che forniscono un costruttore di copia che crea le istanze che non condividere lo stato (o almeno non sembrano farlo) utilizzando una lista di inizializzazione è un buon modo.

Altrimenti dovrete pensare in realtà.

struct alpha {
   beta* m_beta;
   alpha() : m_beta(new beta()) {}
   ~alpha() { delete m_beta; }
   alpha(const alpha& a) {
     // need to copy? or do you have a shared state? copy on write?
     m_beta = new beta(*a.m_beta);
     // wrong
     m_beta = a.m_beta;
   }

Si noti che è possibile ottenere tutto il potenziale segfault utilizzando smart_ptr - ma si può avere un sacco di divertimento debug risultante bug.

Naturalmente, si può ottenere ancora più divertente.

  • I membri che vengono creati su richiesta.
  • new beta(a.beta) è sbagliato nel caso in cui in qualche modo introdurre il polimorfismo.

...una vite la altrimenti - si prega di pensare sempre durante la scrittura di un costruttore di copia.

Perché avete bisogno di getter e setter a tutti?

Semplice :) - conservano invarianti - cioègarantisce la tua classe fa, come "MyString ha sempre un numero pari di caratteri".

Se attuato come previsto, l'oggetto è sempre in uno stato valido, quindi una copia membro può benissimo copiare i membri direttamente senza timore di infrangere alcuna garanzia.Non c'è alcun vantaggio di passare già stato convalidato attraverso un altro giro di convalida dello stato.

Come AraK, ha detto, la cosa migliore sarebbe utilizzando una lista di inizializzazione.


Non è così semplice (1):
Un altro motivo per utilizzare metodi getter/setter è di non fare affidamento su dettagli di implementazione.Che strana idea per una copia CTor, quando la modifica di questi dettagli di implementazione, è quasi sempre necessario regolare CDA in ogni caso.


Non è così semplice (2):
Per dimostrare che mi sbagliavo, è possibile costruire invarianti che sono dipendente nell'istanza stessa, o di un altro fattore esterno.Uno (molto contrieved) esempio:"se il numero di istanze è, anche, la lunghezza della stringa è pari, altrimenti è strano." In questo caso, la copia CTor sarebbe da buttare, o regolare la stringa.In tal caso potrebbe essere d'aiuto utilizzare setter/getter - ma non è il generale cas.Non si deve ricavare regole generali da stranezze.

Io preferisco usare un'interfaccia per esterni classi di accesso ai dati, nel caso in cui si desidera modificare il modo in cui è stato recuperato.Tuttavia, quando si è nell'ambito della classe e desidera replicare lo stato interno del valore copiato, mi piacerebbe andare con dati direttamente i membri.

Per non parlare che probabilmente risparmiare un paio di chiamate di funzione se il getter non sono allineate.

Se il getter sono (in linea e) non virtual, non c'è vantaggi né svantaggi nel loro utilizzo wrt membro diretto accesso -- sembra sciocco a me in termini di stile, ma non un grosso problema, in entrambi i casi.

Se il getter sono virtuali, poi c' è in testa...ma comunque questo è esattamente quando si desidera chiamare, nel caso in cui sei sottoposto a override in una sottoclasse!-)

C'è un test semplice che funziona per molte questioni di progettazione, con questa:aggiungi effetti collaterali e vedere cosa si rompe.

Supponiamo che il setter non solo assegna un valore, ma scrive anche di record di controllo, registra un messaggio o genera un evento.Vuoi che questo accada per ogni proprietà, quando la copia dell'oggetto?Probabilmente non così chiamata setter nel costruttore è logicamente sbagliato (anche se i setter sono infatti solo le assegnazioni).

Anche se sono d'accordo con gli altri manifesti che ci sono molti entry-level C++ "no-no" nel tuo esempio, che la messa di lato e per rispondere alla tua domanda direttamente:

In pratica, io tendo a fare molti, ma non tutti i miei campi di membro* pubblico, per iniziare, e poi spostarli get/set, quando necessario.

Ora, io sarò il primo a dire che questo non è necessariamente una pratica consigliata, e molti professionisti aborrire questo e dire che ogni campo deve avere setter/getter.

Forse.Ma trovo che, in pratica, questo non è sempre necessario.Certo, è causa di dolore in seguito al cambiamento di un campo da pubblico a un getter, e a volte, quando so che tipo di utilizzo in una classe vi sono, ho dato set/get e rendere il campo protetto o privato dall'inizio.

YMMV

RF

  • si chiama "campi di variabili" - vi incoraggio a usare questo termine solo per le variabili locali all'interno di una funzione/metodo di
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top