2016-07-13 87 views
5

Sono uno sviluppatore Java che tenta di acquisire C++. Va bene usare un setter all'interno di un costruttore per riutilizzare i controlli di sanità che fornisce il setter?Utilizzo dei setter nel costruttore

Ad esempio:

#include <stdexcept> 
using namespace std; 

class Test { 
    private: 
     int foo; 
     void setFoo(int foo) { 
      if (foo < 42) { 
       throw invalid_argument{"Foo < 42."}; 
      } 

      this->foo = foo; 
     } 

    public: 
     Test(int foo) { 
      setFoo(foo); 
     }; 
}; 
+0

È valido. Si noti che esistono tipi 'unsigned' per sbarazzarsi del test qui. – Jarod42

+0

Se non si desidera che gli utenti della classe non trasmettano numeri negativi, perché non utilizzare invece 'unsigned'? Quindi il compilatore gestirà il controllo al momento della compilazione per te, invece di aver bisogno di un controllo di runtime. –

+2

La roba 'unsigned' non è indiscussa; vedere per es. https://channel9.msdn.com/Events/GoingNative/2013/Interactive-Panel-Ask-Us-Anything 9:50, 42:40, 1:02:50 (Panel in cui alcuni importanti membri del comitato discutono contro l'uso di numeri interi senza segno per cose diverse dal bit-fiddling). –

risposta

5

Sì, si consiglia di farlo, fondamentalmente per il motivo che hai già menzionato.

D'altra parte dovresti chiederti se hai bisogno del setter e non implementare direttamente i controlli all'interno del costruttore. La ragione per cui sto scrivendo è che i setter in generale danno luogo a uno stato mutabile che ha molti svantaggi rispetto alle classi immutabili. Tuttavia a volte sono richiesti.

Un'altra raccomandazione: Se la variabile di classe è un oggetto ed è possibile modificare la costruzione di questo oggetto, si potrebbe mettere la spunta nella costruzione di questo oggetto:

class MyFoo { 
public: 
    MyFoo(int value) { 
     if (value < 42) { 
      throw invalid_argument{"Foo < 42."}; 
     } 
     v = value; 
    } 
private: 
    int v; 
} 

Questo vi permetterà di utilizzare un elenco di inizializzazione nel costruttore della classe Test:

Test(int foo) : foo(foo) {} 

Tuttavia, ora il controllo è una proprietà della classe della variabile e non più uno della classe proprietaria.

1

Sì, va bene fintanto che senso avere un setter per una particolare variabile membro (abbia una logica che non può essere controllato da cessione solo per esempio). In questo esempio, setFoo potrebbe aver appena preso un unsigned int e il chiamante saprebbe di non passare valori negativi. Che a sua volta potrebbe eliminare il controllo e quindi la necessità di un setter. Per controlli più elaborati, un setter e l'utilizzo di questo setter nel costruttore va bene.

3

Sì, è possibile. Va bene fino a quando i setter non sono virtual, perché è la gerarchia di ereditarietà nel chiamare le funzioni giuste mentre "questo" ptr non è ancora pronto.

Ecco Herb Sutter GotW su questa materia: http://www.gotw.ca/gotw/066.htm

+0

@ user2079303 Sono d'accordo va bene fintanto che dev è consapevole della funzione che verrà chiamata in tale situazione. Non ho mai pensato agli errori derivanti dalla chiamata di funzioni non virtuali che chiamavano onces virtuali. Buon punto – paweldac

+0

@ user2079303: _ "Quello che non devi assolutamente fare è chiamare una funzione membro che a sua volta chiama un membro virtuale" _ Perché no? Com'è diverso da quello che hai appena detto è sicuro? –

+0

@ user2079303: "Così male. I non-costruttori eseguono l'invio dinamico e i costruttori eseguono l'invio dinamico. Il risultato è deterministico. Potrebbe non essere quello che ti aspettavi se sei nel mezzo della costruzione di un oggetto più derivato, ma [è "sicuro" e sicuramente ha lo stesso effetto in entrambi i casi] (http://coliru.stacked-crooked.com/a/9a0f6498a5df9acb). –

1

Risposta breve: Sì. In effetti, il tuo esempio funziona.

Risposta lunga: ma non è una buona pratica. Almeno, devi stare attento.

In generale, una funzione impostata funziona con un oggetto costruito. Si suppone che l'invariante della classe regga. Le funzioni di una classe sono implementate considerando che l'invariante è vero.

Se si desidera utilizzare altre funzioni in un costruttore, è necessario scrivere del codice. Ad esempio, per creare un oggetto vuoto.

Ad esempio, se nella tua classe cambi setFoo in futuro (diciamo setFoo cambia il membro foo solo che è più grande) l'esempio smette di funzionare.

0

So che questo non si adatta alla vostra situazione. È solo per ragioni di completezza:

Quando si impostano semplicemente i valori dei membri (senza controlli come il proprio in setFoo) si consiglia di utilizzare gli elenchi di inizializzazione nel costruttore. Questo impedisce ai membri di essere "inizializzati" 2 volte: 1. con il loro valore predefinito, 2. con il valore che hai passato al costruttore.

class Test { 
private: 
    int foo_; 

public: 
    Test(int foo) 
     : foo_(foo) 
    { }; 
};